#' Check if a package is available
#' @keywords internal
is_package_available <- function(pkg) {
  requireNamespace(pkg, quietly = TRUE)
}

#' Get measure row names
#' @keywords internal
get_measure_names <- function() {
  c("N", "R sq.", "Adj. R sq.", "AIC")
}

#' Get indices of model statistic rows
#' @keywords internal
get_measure_row_indices <- function(table) {
  stat_terms <- attr(table, "stat_terms")

  if (is.null(stat_terms) || length(stat_terms) == 0) {
    stat_terms <- get_measure_names()
  }

  which(table$term %in% stat_terms)
}

#' Get first model statistic row index
#' @keywords internal
get_first_measure_row <- function(table) {
  idx <- get_measure_row_indices(table)
  if (length(idx) == 0) {
    return(NA_integer_)
  }
  min(idx)
}

#' Wrap interaction terms to reduce term-column width
#'
#' For Word output, `"x1 * x2"` becomes `"x1\\n× x2"`.
#'
#' @param terms Character vector of displayed term labels.
#' @param output Output backend used for display formatting. `"word"` uses a
#'   literal multiplication sign, while `"latex"` uses a LaTeX-safe
#'   multiplication symbol.
#'
#' @return Character vector with interaction terms wrapped for display.
#'
#' @keywords internal
wrap_interaction_terms <- function(terms, output = c("word", "latex")) {
  output <- match.arg(output)

  operator <- switch(
    output,
    word = "\u00d7 ",
    latex = "\\(\\times\\) "
  )

  gsub(" * ", paste0("\n", operator), terms, fixed = TRUE)
}

#' Abbreviate variable name
#'
#' Deterministic abbreviation rules:
#' - (Intercept) is preserved
#' - Single-token names truncate to 6 characters
#' - "_" and "." are treated as separators
#' - Two-token names become `token1[1:4] . token2[1:4]`
#' - Three-plus-token names become `t1[1:2] . t2[1:2] . t3[1:2]`
#'
#' @param var_name Character string
#' @param is_intercept Logical indicating if this variable is the intercept
#' @return Character string
#' @keywords internal
abbreviate_var_name <- function(var_name, is_intercept = FALSE) {

  if (is.na(var_name) || !nzchar(var_name)) return(var_name)

  # Preserve intercept label using metadata
  if (is_intercept) return(var_name)

  take <- function(x, k) {
    if (nchar(x) <= k) x else substring(x, 1, k)
  }

  # Treat "_" and "." as separators for composite names
  if (grepl("[_.]", var_name, perl = TRUE)) {

    tokens <- unlist(strsplit(var_name, "[_.]", perl = TRUE))
    tokens <- tokens[tokens != ""]

    if (length(tokens) == 0) return(take(var_name, 6))

    if (length(tokens) == 1) {
      return(take(tokens[1], 6))
    }

    if (length(tokens) == 2) {
      return(paste0(take(tokens[1], 4), ".", take(tokens[2], 4)))
    }

    return(paste0(take(tokens[1], 2), ".", take(tokens[2], 2), ".", take(tokens[3], 2)))
  }

  # Single-token name
  take(var_name, 6)
}

#' Ensure labels are unique via numeric suffix
#' @keywords internal
make_unique_labels <- function(labels) {

  if (!anyDuplicated(labels)) return(labels)

  out <- labels
  tab <- table(labels)
  dup_names <- names(tab[tab > 1])

  for (nm in dup_names) {
    idx <- which(labels == nm)
    if (length(idx) > 1) {
      out[idx[-1]] <- paste0(nm, seq_along(idx)[-1])
    }
  }

  out
}

#' Detect and split factor level suffix using model metadata
#' @keywords internal
split_factor_level <- function(term, levels_map = NULL) {

  if (is.null(levels_map) || length(levels_map) == 0) return(term)
  if (!nzchar(term) || is.na(term)) return(term)
  if (grepl(":", term, fixed = TRUE)) return(term)

  for (var in names(levels_map)) {

    levs <- levels_map[[var]]
    if (is.null(levs) || length(levs) == 0) next

    if (!startsWith(term, var)) next

    suffix <- substring(term, nchar(var) + 1)

    if (suffix %in% levs) {
      return(paste0(var, ":", suffix))
    }
  }

  term
}

#' Format term labels for display
#'
#' Transformations:
#' - Preserve polynomial suffixes (.L, .Q, .C, etc.)
#' - Split factor levels using levels_map
#' - Abbreviate only variable portion (if abbreviate = TRUE)
#' - Ensure uniqueness after abbreviation
#'
#' @param terms Character vector of displayed term labels.
#' @param row_types Optional character vector of row types
#' @param levels_map List mapping factor names to their levels
#' @param abbreviate Logical. Apply abbreviation? Default FALSE
#' @keywords internal
format_term_labels <- function(terms, row_types = NULL, levels_map = NULL, abbreviate = FALSE) {

  formatted <- terms

  # ---- Step 0: Normalize model interaction syntax ----
  # broom encodes interactions as "var1:var2". Convert to "var1 * var2"
  # before splitting factor levels. Factor levels are handled later via
  # split_factor_level() and levels_map (they arrive concatenated, e.g., gendermale).
  formatted <- gsub(":", " * ", formatted, fixed = TRUE)

  # ---- Step 1: Split factor levels ----
  for (i in seq_along(formatted)) {

    term <- formatted[i]

    if (grepl("\\*", term)) {

      parts <- strsplit(term, " \\* ")[[1]]

      for (j in seq_along(parts)) {

        part <- trimws(parts[j])

        if (!grepl(":", part, fixed = TRUE)) {
          parts[j] <- split_factor_level(part, levels_map)
        }
      }

      formatted[i] <- paste(parts, collapse = " * ")

    } else {

      if (!grepl(":", term, fixed = TRUE)) {
        formatted[i] <- split_factor_level(term, levels_map)
      }
    }
  }

  # ---- Step 2: Apply abbreviations (only if requested) ----
  if (isTRUE(abbreviate)) {

    # Helper: abbreviate variable portion only
    abbrev_piece <- function(piece, is_intercept = FALSE) {

      piece <- trimws(piece)

      if (is_intercept) return(piece)

      # Preserve polynomial suffixes like ".L", ".Q", ".C", ".^4"
      if (grepl("\\.[A-Za-z^0-9]+$", piece)) {
        base <- sub("(\\.[A-Za-z^0-9]+)$", "", piece)
        suffix <- sub("^.*(\\.[A-Za-z^0-9]+)$", "\\1", piece)
        return(paste0(abbreviate_var_name(base, is_intercept), suffix))
      }

      if (grepl(":", piece, fixed = TRUE)) {
        parts <- strsplit(piece, ":", fixed = TRUE)[[1]]
        var_name <- parts[1]
        level_part <- paste(parts[-1], collapse = ":")
        # Replace underscores with periods in level part, then abbreviate
        level_part <- gsub("_", ".", level_part, fixed = TRUE)
        level_part <- abbreviate_var_name(level_part, is_intercept)
        return(paste0(abbreviate_var_name(var_name, is_intercept), ":", level_part))
      }

      abbreviate_var_name(piece, is_intercept)
    }

    for (i in seq_along(formatted)) {

      term <- formatted[i]
      is_idx_intercept <- !is.null(row_types) && row_types[i] == "intercept"

      if (grepl("\\*", term)) {

        parts <- strsplit(term, " \\* ")[[1]]
        parts <- vapply(parts, function(p) abbrev_piece(p, is_idx_intercept), character(1))
        formatted[i] <- paste(parts, collapse = " * ")

      } else {

        formatted[i] <- abbrev_piece(term, is_idx_intercept)
      }
    }

    # ---- Step 3: Ensure uniqueness ----
    formatted <- make_unique_labels(formatted)
  }

  formatted
}

build_levels_map <- function(model_list) {
  out <- list()

  for (m in model_list) {
    xl <- m$xlevels
    if (is.null(xl) || length(xl) == 0) next

    for (nm in names(xl)) {
      out[[nm]] <- unique(c(out[[nm]], xl[[nm]]))
    }
  }

  out
}
