#' Build the Model Specification Function
#'
#' @description
#' This internal helper uses metaprogramming to construct a complete R function
#' that acts as a `parsnip` model specification (e.g., `my_mlp()`).
#'
#' @details
#' The process involves three main steps:
#' 1.  **Function Body Construction**: An expression for the function body is
#'     created. This body uses `rlang::enquo()` and `rlang::enquos()` to
#'     capture all user-provided arguments (both named and via `...`) into a
#'     list of quosures. This list is then passed to
#'     `parsnip::new_model_spec()`.
#' 2.  **Function Signature Construction**: A formal argument list is created
#'     from `all_args`, and `...` is added to allow passthrough arguments.
#'     `rlang::new_function()` combines the signature and body into a new
#'     function object.
#' 3.  **Documentation Attachment**: `generate_roxygen_docs()` creates a
#'     comprehensive Roxygen comment block as a string, which is then attached
#'     to the new function using `comment()`.
#'
#' @param model_name The name of the model specification function to create
#'   (e.g., "my_mlp").
#' @param mode The model mode ("regression" or "classification").
#' @param all_args A named list of formal arguments for the new function's
#'   signature, as generated by `collect_spec_args()`. The values are typically
#'   `rlang::missing_arg()` or `rlang::zap()`.
#' @param parsnip_names A character vector of all argument names that should be
#'   captured as quosures and passed to `parsnip::new_model_spec()`.
#' @param layer_blocks The user-provided list of layer block functions. This is
#'   passed directly to `generate_roxygen_docs()` to create documentation for
#'   block-specific parameters.
#' @param functional A logical indicating if the model is functional
#'   (for `create_keras_functional_spec()`) or sequential. This is passed to
#'   `generate_roxygen_docs()` to tailor the documentation.
#' @return A new function object with attached Roxygen comments, ready to be
#'   placed in the user's environment.
#' @noRd
build_spec_function <- function(
  model_name,
  mode,
  all_args,
  parsnip_names,
  layer_blocks,
  functional = FALSE
) {
  quos_exprs <- purrr::map(
    parsnip_names,
    ~ rlang::expr(rlang::enquo(!!rlang::sym(.x)))
  )
  names(quos_exprs) <- parsnip_names

  quos_call <- rlang::call2("quos", rlang::sym("..."), .ns = "rlang")

  # Capture at spec-creation time so every instance carries the data needed
  # for auto-registration in predict.kerasnip_model_fit.
  captured_layer_blocks <- layer_blocks
  captured_functional <- functional

  body <- rlang::expr({
    # Capture both explicit args and ... to pass to the fit impl
    # Named arguments are captured into a list of quosures.
    main_args <- rlang::list2(!!!quos_exprs)
    # ... arguments are captured into a separate list of quosures.
    dot_args <- !!quos_call
    args <- c(main_args, dot_args)
    result <- parsnip::new_model_spec(
      !!model_name,
      args = args,
      eng_args = NULL,
      mode = !!mode,
      method = NULL,
      engine = NULL
    )
    class(result) <- c(class(result)[1L], "kerasnip_spec", class(result)[-1L])
    attr(result, "kerasnip_layer_blocks") <- captured_layer_blocks
    attr(result, "kerasnip_functional") <- captured_functional
    result
  })

  # Add ... to the function signature to capture any other compile arguments
  fn_args <- c(all_args, list(... = rlang::missing_arg()))

  fn <- rlang::new_function(args = fn_args, body = body)

  docs <- generate_roxygen_docs(
    model_name,
    layer_blocks,
    all_args,
    functional = functional
  )
  comment(fn) <- docs
  fn
}
