PI_METHODS   = c("rca", "uca", "dblcent", "pind", "ca", "custom")
DA_METHODS   = c("lms", "qml")
MPLUS_METHOD = "mplus"


#' Estimate interaction effects in structural equation models (SEMs)
#'
#' @param model.syntax \code{lavaan} syntax
#'
#' @param data dataframe
#'
#' @param method method to use:
#' \describe{
#'   \item{\code{"dblcent"}}{double centering approach (passed to \code{lavaan}).}
#'   \item{\code{"ca"}}{constrained approach (passed to \code{lavaan}).}
#'   \item{\code{"rca"}}{residual centering approach (passed to \code{lavaan}).}
#'   \item{\code{"uca"}}{unconstrained approach (passed to \code{lavaan}).}
#'   \item{\code{"pind"}}{prod ind approach, with no constraints or centering (passed to \code{lavaan}).}
#'   \item{\code{"lms"}}{latent moderated structural equations (not passed to \code{lavaan}).}
#'   \item{\code{"qml"}}{quasi maximum likelihood estimation (not passed to \code{lavaan}).}
#'   \item{\code{"custom"}}{use parameters specified in the function call (passed to \code{lavaan}).}
#'   \item{\code{"mplus"}}{estimate model through \code{Mplus}.}
#' }
#'
#' @param ... arguments passed to other functions depending on the method (see \code{\link{modsem_pi}}, \code{\link{modsem_da}}, and \code{\link{modsem_mplus}})
#'
#' @return \code{modsem} object with class \code{\link{modsem_pi}}, \code{\link{modsem_da}}, or \code{\link{modsem_mplus}}
#'
#' @export
#' @description
#' \code{modsem()} is a function for estimating interaction effects between latent variables
#' in structural equation models (SEMs).
#' Methods for estimating interaction effects in SEMs can basically be split into two frameworks:
#' \enumerate{
#'   \item Product Indicator (PI) based approaches (\code{"dblcent"}, \code{"rca"}, \code{"uca"}, \code{"ca"}, \code{"pind"})
#'   \item Distributionally (DA) based approaches (\code{"lms"}, \code{"qml"}).
#' }
#'
#' For the product indicator-based approaches, \code{modsem()} is essentially a fancy wrapper for \code{lavaan::sem()} which generates the
#' necessary syntax and variables for the estimation of models with latent product indicators.
#'
#' The distributionally based approaches are implemented separately and are
#' not estimated using \code{lavaan::sem()}, but rather using custom functions (largely
#' written in \code{C++} for performance reasons). For greater control, it is advised that
#' you use one of the sub-functions (\link{modsem_pi}, \link{modsem_da}, \link{modsem_mplus}) directly,
#' as passing additional arguments to them via \code{modsem()} can lead to unexpected behavior.
#'
#' @examples
#' library(modsem)
#' # For more examples, check README and/or GitHub.
#' # One interaction
#' m1 <- '
#'   # Outer Model
#'   X =~ x1 + x2 +x3
#'   Y =~ y1 + y2 + y3
#'   Z =~ z1 + z2 + z3
#'
#'   # Inner model
#'   Y ~ X + Z + X:Z
#' '
#'
#' # Double centering approach
#' est1 <- modsem(m1, oneInt)
#' summary(est1)
#'
#' \dontrun{
#' # The Constrained Approach
#' est1_ca <- modsem(m1, oneInt, method = "ca")
#' summary(est1_ca)
#'
#' # LMS approach
#' est1_lms <- modsem(m1, oneInt, method = "lms")
#' summary(est1_lms)
#'
#' # QML approach
#' est1_qml <- modsem(m1, oneInt, method = "qml")
#' summary(est1_qml)
#' }
#'
#' # Theory Of Planned Behavior
#' tpb <- '
#' # Outer Model (Based on Hagger et al., 2007)
#'   ATT =~ att1 + att2 + att3 + att4 + att5
#'   SN =~ sn1 + sn2
#'   PBC =~ pbc1 + pbc2 + pbc3
#'   INT =~ int1 + int2 + int3
#'   BEH =~ b1 + b2
#'
#' # Inner Model (Based on Steinmetz et al., 2011)
#'   INT ~ ATT + SN + PBC
#'   BEH ~ INT + PBC
#'   BEH ~ INT:PBC
#' '
#'
#' # Double centering approach
#' est_tpb <- modsem(tpb, data = TPB)
#' summary(est_tpb)
#'
#' \dontrun{
#' # The Constrained Approach
#' est_tpb_ca <- modsem(tpb, data = TPB, method = "ca")
#' summary(est_tpb_ca)
#'
#' # LMS approach
#' est_tpb_lms <- modsem(tpb, data = TPB, method = "lms", nodes = 32)
#' summary(est_tpb_lms)
#'
#' # QML approach
#' est_tpb_qml <- modsem(tpb, data = TPB, method = "qml")
#' summary(est_tpb_qml)
#' }
modsem <- function(model.syntax = NULL,
                   data = NULL,
                   method = "dblcent",
                   ...) {
  method <- tolower(method)

  if (is.null(model.syntax)) {
    mod_msg_stop("No model.syntax provided")
  } else if (!is.character(model.syntax)) {
    mod_msg_stop("The provided model syntax is not a string!")
  } else if (length(model.syntax) > 1) {
    mod_msg_stop("The provided model syntax is not of length 1")
  }

  if (is.null(data)) {
    mod_msg_stop("No data provided")
  } else {
    data <- as.data.frame(data)
  }

  if (method %in% PI_METHODS) {
    modsem_pi(model.syntax, data = data, method = method, ...)
  } else if (method %in% DA_METHODS) {
    modsem_da(model.syntax, data = data, method = method, ...)
  } else if (method == MPLUS_METHOD) {
    modsem_mplus(model.syntax, data = data, ...)
  } else {
    mod_msg_stop("Method not recognized")
  }
}
