# ============================================================================
# CLDedgelister - public API
# ============================================================================

#' Supported model formats
#'
#' @return A character vector of the format labels the package understands.
#' @examples
#' cld_formats()
#' @export
cld_formats <- function() {
  c("Vensim (.mdl)",
    "Stella / iThink (.stmx, .itmx, XMILE)",
    "Powersim Studio (XMILE .xml)",
    "AnyLogic (.alp)",
    "GoldSim (exported .xml)")
}

# guess the format from the file extension
.detect_format <- function(path) {
  ext <- tolower(tools::file_ext(path))
  switch(ext,
         "mdl"   = "Vensim (.mdl)",
         "2mdl"  = "Vensim (.mdl)",
         "stmx"  = "Stella / iThink (.stmx, .itmx, XMILE)",
         "itmx"  = "Stella / iThink (.stmx, .itmx, XMILE)",
         "xmile" = "Stella / iThink (.stmx, .itmx, XMILE)",
         "alp"   = "AnyLogic (.alp)",
         "xml"   = "Powersim Studio (XMILE .xml)",  # try XMILE first for .xml
         NA_character_)
}

#' Convert a system-dynamics model file to an edge list
#'
#' Reads a model from Vensim, Stella / iThink, Powersim Studio, AnyLogic or
#' GoldSim and returns a two-column data.frame of INPUT (cause) and OUTPUT
#' (effect) links - the same edge list produced by the SDM Network Analyzer.
#'
#' @param path Path to the model file.
#' @param format One of \code{cld_formats()}. If \code{NULL} (default) the
#'   format is guessed from the file extension.
#' @return A data.frame with columns \code{INPUT} and \code{OUTPUT}. Node
#'   diagram positions, when available, are attached as
#'   \code{attr(x, "positions")}.
#' @examples
#' # Convert the bundled example Vensim model to an edge list
#' mdl <- system.file("extdata", "example_model.mdl", package = "CLDedgelister")
#' el <- model_to_edgelist(mdl)
#' head(el)
#' @export
model_to_edgelist <- function(path, format = NULL) {
  if (!file.exists(path)) stop("File not found: ", path)
  if (is.null(format) || is.na(format)) {
    format <- .detect_format(path)
    if (is.na(format))
      stop("Could not determine the model format from the extension. ",
           "Pass `format = ` explicitly; see cld_formats().")
  }

  df <- tryCatch({
    if (startsWith(format, "Vensim")) {
      parse_vensim(path)
    } else if (startsWith(format, "Stella") ||
               startsWith(format, "Powersim")) {
      # both are XMILE; for a generic .xml try XMILE, fall back to GoldSim
      out <- tryCatch(parse_xmile(path), error = function(e) NULL)
      if (is.null(out) || nrow(out) == 0) parse_goldsim(path) else out
    } else if (startsWith(format, "AnyLogic")) {
      parse_anylogic(path)
    } else if (startsWith(format, "GoldSim")) {
      parse_goldsim(path)
    } else {
      stop("Unknown format: ", format)
    }
  }, error = function(e) {
    stop("Failed to parse the model (", format, "): ", conditionMessage(e))
  })

  if (nrow(df) == 0)
    warning("No cause-effect links were extracted. If this is a Powersim or ",
            "GoldSim model, export it to XMILE (.xmile) first - that is the ",
            "most reliable format.")
  df
}

#' Convert a model file and write the edge list to disk
#'
#' @param path Path to the model file.
#' @param out_path Output file (.csv or .xlsx). If \code{NULL}, the model's
#'   name with an \code{_edgelist.csv} suffix is used.
#' @param format See \code{\link{model_to_edgelist}}.
#' @return (invisibly) the edge list data.frame.
#' @examples
#' mdl <- system.file("extdata", "example_model.mdl", package = "CLDedgelister")
#' out <- tempfile(fileext = ".csv")
#' model_to_edgelist_file(mdl, out)
#' @export
model_to_edgelist_file <- function(path, out_path = NULL, format = NULL) {
  df <- model_to_edgelist(path, format)
  if (is.null(out_path)) {
    stem <- tools::file_path_sans_ext(basename(path))
    out_path <- file.path(dirname(path), paste0(stem, "_edgelist.csv"))
  }
  ext <- tolower(tools::file_ext(out_path))
  if (ext == "xlsx") {
    if (!requireNamespace("writexl", quietly = TRUE))
      stop("Writing .xlsx needs the 'writexl' package. ",
           "install.packages('writexl') or use a .csv output.")
    writexl::write_xlsx(df, out_path)
  } else {
    utils::write.csv(df, out_path, row.names = FALSE)
  }
  message("Wrote ", nrow(df), " edges to ", out_path)
  invisible(df)
}
