#' Create Multi-Format Regression Tables
#'
#' Takes model objects as arguments and creates formatted tables for
#' Word or LaTeX/PDF output. Supports robust standard errors,
#' marginal effects, and control variable grouping.
#'
#' @param ... Statistical model objects (lm or glm). Pass models directly
#'   like \code{easytable(m1, m2, m3)}.
#' @param model.names Character vector of custom names for model columns.
#'   If NULL (default), columns are named "Model 1", "Model 2", etc.
#'   Length must match number of models.
#' @param output Character string specifying output format. One of:
#'   \itemize{
#'     \item \code{"word"} - Microsoft Word via flextable (default)
#'     \item \code{"latex"} - LaTeX for PDF output
#'   }
#' @param export.word Character string ending in \code{.docx} for Word file export.
#'   Only supported when \code{output = "word"}. If NULL (default), no file is written.
#' @param export.csv Character string ending in \code{.csv} for CSV export.
#'   If NULL (default), no CSV file is written.
#' @param robust.se Logical. Use robust standard errors (HC type)? Default FALSE.
#'   Requires packages: lmtest, sandwich
#' @param control.var Character vector of variable names to group as "control
#'   variables". These will be collapsed into single rows showing "Y" for
#'   presence instead of individual coefficients. Default NULL.
#' @param margins Logical. Compute average marginal effects (AME)? Default FALSE.
#'   Requires package: margins
#' @param highlight Logical. Highlight significant coefficients (positive in green,
#'   negative in red)? Default FALSE. Works best with Word output.
#' @param abbreviate Logical. Abbreviate variable names for readability? Default FALSE.
#'   When TRUE, long variable names are shortened using deterministic rules.
#' @param table_size Character string specifying LaTeX table size. Only works with
#'   \code{output = "latex"}. Options: "tiny", "small", "normalsize", "scriptsize".
#'   Default "normalsize". Error if used with Word output.
#' @param digits Number of digits after the decimal point for coefficients and
#'   standard errors, including the mantissa in scientific notation. Allowed
#'   values are 0 to 4. Default 2.
#' @param custom.row Optional character vector for an additional row placed at
#'   the bottom of the statistics block. The first element is the row label and
#'   each subsequent element is the value for the corresponding model column.
#'   The vector must have exactly one more element than the number of models.
#'   Default NULL (no extra row).
#'
#' @return
#' Depends on \code{output}:
#' \itemize{
#'   \item \code{"word"} - A flextable object
#'   \item \code{"latex"} - Character string with LaTeX table code
#' }
#'
#' @details
#' The function extracts coefficients, standard errors, and p-values from each
#' model, adds significance stars (*** p<.01, ** p<.05, * p<.1), and includes
#' model fit statistics such as N, R-squared, Adjusted R-squared, and
#' AIC (for \code{glm} models).
#'
#' Control variables can be grouped to show presence/absence rather than
#' individual coefficients for each factor level or transformation.
#'
#' Term labels are automatically formatted for readability:
#' \itemize{
#'   \item Factor levels separated with colon (e.g., \code{advisor_confidence:low})
#'   \item Interactions shown with a multiplication sign (e.g., \code{var1 × var2})
#'   \item Polynomial contrasts as L indices (e.g., \code{var:L1}, \code{var:L2})
#'   \item Long variable names abbreviated for clarity
#' }
#'
#' @section Dependencies:
#' \itemize{
#'   \item Always required: dplyr
#'   \item Word output: flextable
#'   \item LaTeX output: knitr, optionally kableExtra for enhanced formatting
#'   \item Robust SE: lmtest, sandwich
#'   \item Marginal effects: margins
#' }
#'
#' @export
easytable <- function(...,
                      model.names = NULL,
                      output = "word",
                      export.word = NULL,
                      export.csv = NULL,
                      robust.se = FALSE,
                      control.var = NULL,
                      margins = FALSE,
                      highlight = FALSE,
                      abbreviate = FALSE,
                      table_size = "normalsize",
                      digits = 2,
                      custom.row = NULL) {

  # Capture models from dots
  models <- list(...)

  # Validate that we have at least one model
  if (length(models) == 0) {
    stop("No models were provided. Try: easytable(m1, m2, m3)",
         call. = FALSE)
  }

  # Build model names
  if (is.null(model.names)) {
    # Default: "Model 1", "Model 2", etc.
    model_names <- paste("Model", seq_along(models))
  } else {
    # Validate user-provided names
    if (!is.character(model.names)) {
      stop("`model.names` must be a character vector, for example c(\"Model 1\", \"Model 2\").", call. = FALSE)
    }
    if (length(model.names) != length(models)) {
      stop(
        sprintf(
          "`model.names` has %d value(s), but you supplied %d model(s).",
          length(model.names), length(models)
        ),
        call. = FALSE
      )
    }
    model_names <- model.names
  }

  # Create named list
  names(models) <- model_names
  model_list <- models

  # Validate and normalize output parameter
  output <- validate_output_format(output)

  # Validate inputs
  validate_model_list(model_list)
  validate_model_types(model_list)
  validate_control_vars(model_list, control.var)
  validate_parameters(robust.se, margins, highlight, export.word, export.csv, output)
  validate_table_size(table_size, output)
  validate_digits(digits)
  validate_custom_row(custom.row, length(model_list))

  # Check feature dependencies
  check_robust_dependencies(robust.se)
  check_margins_dependencies(margins)
  check_format_dependencies(output)

  # Parse models (extract coefficients, SE, p-values)
  parsed <- parse_models(model_list, robust.se, margins, digits)

  # Transform table (handle control vars, sort, organize)
  transformed <- transform_table(parsed, control.var, abbreviate)

  # Append custom row below AIC if provided
  if (!is.null(custom.row)) {
    # model_cols should exclude row_type
    model_cols <- setdiff(names(transformed), c("term", "row_type"))
    new_row <- data.frame(term = custom.row[1], stringsAsFactors = FALSE)
    for (i in seq_along(model_cols)) {
      new_row[[model_cols[i]]] <- custom.row[i + 1L]
    }
    new_row$row_type <- "custom"

    transformed <- rbind(transformed, new_row)
    existing_stat_terms <- attr(transformed, "stat_terms")
    attr(transformed, "stat_terms") <- c(existing_stat_terms, custom.row[1])
  }

  # Export to CSV if requested
  if (!is.null(export.csv)) {
    csv_table <- transformed
    csv_table$row_type <- NULL
    for (col in names(csv_table)) {
      if (is.character(csv_table[[col]])) {
        csv_table[[col]] <- gsub("\n", " ", csv_table[[col]], fixed = TRUE)
      }
    }
    write.csv(
      csv_table,
      file = export.csv,
      row.names = FALSE
    )
  }

  # Format based on output type
  result <- switch(
    output,
    word = format_word(transformed, robust.se, margins, highlight),
    latex = format_latex(transformed, robust.se, margins, highlight, table_size)
  )

  # Export Word file if requested
  if (!is.null(export.word)) {
    flextable::save_as_docx(`Regression Table` = result, path = export.word)
  }

  return(result)
}
