#' Fit a Neutral Model to Microbial Community Data
#'
#' The function fits the neutral distribution model developed by Sloan et al.
#' (2006), and implemented in R by Burns et al. (2015), to an OTU/ASV table and
#' returns several goodness of fit statistics alongside a data.frame
#' with predicted occurrence frequencies for each OTU/ASV based on their
#' abundance in the metacommunity for plotting the abundance-occupancy
#' distribution. In addition, this function identified core and not-core
#' OTU/ASVs that are neutral (i.e. stochastically or randomly distributed)
#' above (i.e. interpreted as deterministically or predictably selected) and
#' below (i.e. interpreted as dispersally limited) the model predictions.
#'
#' @param otu_table A community count matrix (or OTU table) with samples as
#' rows and OTU/ASVs as columns..
#' @param core_set Character vector of core OTU/ASVs IDs (must match column
#' names of `otu_table`).
#' @param abundance_occupancy data.frame (or tibble) with OTU/ASVs names,
#'  occupancy (`otu_occ`),
#'  and mean relative abundance (`otu_rel`) as generated by
#' \link[=identify_core]{identify_core()}.
#'
#' @return A list with:
#'   - `goodness_of_fit`: one-row tibble of summary stats (plus `above.pred`,
#'  `below.pred`) - `model_prediction`: tibble with `abundance_occupancy`
#' joined to per-taxon predictions
#'
#' @references
#' Sloan, W. T., Lunn, M., Woodcock, S., Head, I. M., Nee, S., & Curtis, T. P.
#' (2006). Quantifying the roles of immigration and chance in shaping
#' prokaryote community structure. Environmental Microbiology, 8(4), 732–740.
#' <doi:10.1111/j.1462-2920.2005.00956.x>
#'
#' Burns, A. R., Stephens, W. Z., Stagaman, K., Wong, S., Rawls, J. F.,
#' Guillemin, K., & Bohannan, B. J. M. (2016). Contribution of neutral
#' processes to the assembly of gut microbial communities in the zebrafish over
#' host development. The ISME Journal, 10(3), 655–664.
#' <doi:10.1038/ismej.2015.142>
#'
#' Shade A, Stopnisek N (2019) Abundance-occupancy distributions to prioritize
#' plant core microbiome membership. Current Opinion in Microbiology, 49:50-58
#' <doi:10.1016/j.mib.2019.09.008>
#'
#' @seealso [plot_neutral_model()]
#'
#' @examples
#' library(BRCore)
#' data("switchgrass_core", package = "BRCore")
#'
#' switchgrass_core_fit <- fit_neutral_model(
#'   otu_table = switchgrass_core$otu_table,
#'   core_set = switchgrass_core$increase_core,
#'   abundance_occupancy = switchgrass_core$abundance_occupancy
#' )
#'
#' str(switchgrass_core_fit)
#' switchgrass_core_fit$goodness_of_fit
#' switchgrass_core_fit$model_prediction |>
#' head()
#'
#' @importFrom stats confint pbeta pbinom ppois dnorm AIC BIC coef
#' @importFrom stats4 mle
#' @importFrom minpack.lm nlsLM
#' @importFrom Hmisc binconf
#'
#' @export
fit_neutral_model <- function(
  otu_table,
  core_set,
  abundance_occupancy
) {
  # input checks ----
  if (!is.matrix(otu_table) && !is.data.frame(otu_table)) {
    cli::cli_abort(
      "`otu_table` must be a matrix or data.frame (samples x taxa)."
    )
  }

  spp <- as.matrix(t(otu_table))

  core_taxa <- as.character(if (is.null(core_set)) character() else core_set)
  if (!length(core_taxa)) {
    cli::cli_abort(
      "`core_set` must be a non-empty character vector of ASV/OTU IDs."
    )
  }

  if (!("otu" %in% colnames(abundance_occupancy))) {
    cli::cli_abort(
      "`abundance_occupancy` must contain a column named {.field otu} to join predictions."
    )
  }

  # add membership (plot-friendly labels) ----
  occ_abun <- abundance_occupancy |>
    dplyr::mutate(
      membership = ifelse(.data$otu %in% core_taxa, "Core", "Not core")
    )

  # fit model ----
  obs.np <- sncm.fit(spp, core_taxa, stats = FALSE, pool = NULL)
  sta.np <- suppressMessages(
    sncm.fit(spp, core_taxa, stats = TRUE, pool = NULL)
  ) # Avoid printing of model fitting messages again, see sncm.fit() for details

  if (is.null(rownames(obs.np))) {
    cli::cli_abort(
      "Per-taxon predictions must have rownames as ASV/OTU IDs."
    )
  }
  obs_tbl <- tibble::as_tibble(obs.np, rownames = "otu")

  # ASV/OTUs in/out prediction intervals ----
  obs_tbl$fit_class <- "As predicted"
  obs_tbl[
    which(obs_tbl$freq < obs_tbl$pred.lwr),
    "fit_class"
  ] <- "Below prediction"
  obs_tbl[
    which(obs_tbl$freq > obs_tbl$pred.upr),
    "fit_class"
  ] <- "Above prediction"
  obs_tbl[which(is.na(obs_tbl$freq)), "fit_class"] <- "NA"
  fit_table <- as.data.frame(dplyr::left_join(occ_abun, obs_tbl, by = "otu"))

  # compute above/below proportions ----
  sta.np$above.pred <- sum(obs_tbl$freq > (obs_tbl$pred.upr), na.rm = TRUE) /
    sta.np$Richness
  sta.np$below.pred <- sum(obs_tbl$freq < (obs_tbl$pred.lwr), na.rm = TRUE) /
    sta.np$Richness
  fit_res <- as.data.frame(sta.np)
  # colnames(fit_res) <- "statistics"

  # return ----
  out <- list(
    goodness_of_fit = fit_res,
    model_prediction = fit_table
  )
  class(out) <- c("fit_neutral_model", class(out))

  cli::cli_alert_success("Model fitting complete!")

  out
}
