#' Generate an HTCondor executable shell script for an R job
#'
#' `htc_gen_executable()` writes a ready-to-use bash script (`.sh`) that
#' HTCondor runs inside the container for each job. The script creates a
#' results folder, runs the R script via `Rscript`, and compresses the
#' results into a tarball for transfer back to the submit node.
#'
#' @param output_file A character string. Name of the shell script to write.
#'   Must end in `".sh"`. Defaults to `"job.sh"`.
#' @param r_script A character string. Name of the R script that HTCondor
#'   will run inside the container, e.g. `"analysis.R"`. Must be supplied
#'   explicitly -- there is no default. If you used
#'   `toolero::create_qmd(use_purl = TRUE)`, the script is the `.R` file
#'   produced by `purl.R` after rendering.
#' @param results_folder A character string. Name of the folder created
#'   inside the container to hold job outputs before compression. Defaults
#'   to `"results-folder"`.
#' @param mode A character string. Execution mode. `"single"` (the default)
#'   runs the R script without a positional argument, producing a single
#'   fixed-name results tarball. `"multiple"` passes the subset filename
#'   as a positional argument via `${1}`, producing a per-job tarball named
#'   `${1}-results.tar.gz`. Must match the `mode` used in
#'   [htc_gen_submit()].
#' @param verbose Logical. If `TRUE`, prints progress messages as each
#'   section of the script is written. Defaults to `FALSE`.
#' @param comments Logical. If `TRUE`, annotates each section with an
#'   explanatory comment describing what the line does. Useful for
#'   researchers learning the HTCondor executable script conventions.
#'   Defaults to `FALSE`.
#' @param set_executable Logical. If `TRUE`, sets executable permissions on
#'   the generated script via `Sys.chmod()` so the file is ready to copy to
#'   the CHTC submit node without any additional steps. Defaults to `TRUE`.
#'   Set to `FALSE` if you prefer to manage permissions manually, in which
#'   case you must run `chmod +x` on the script before submitting your job.
#' @param output A character string. Directory where the shell script will
#'   be written. Defaults to `"."` (current working directory).
#'
#' @return Called for its side effects. Writes a bash script to
#'   `file.path(output, output_file)` and sets executable permissions when
#'   `set_executable = TRUE`. Returns `invisible(NULL)`.
#'
#' @section Relationship to htc_gen_submit():
#' The executable script generated by `htc_gen_executable()` is the file
#' referenced by the `executable` argument in [htc_gen_submit()]. The two
#' functions should always use the same `mode`. In `"multiple"` mode,
#' HTCondor passes each subset filename to the script as `${1}`, which is
#' forwarded to the R script as a positional argument. The R script must
#' be written to accept this argument -- the recommended approach is
#' `toolero::detect_execution_context()`:
#'
#' ```r
#' context <- toolero::detect_execution_context()
#'
#' input_file <- switch(context,
#'   interactive = "data/penguins.csv",
#'   quarto      = params$input_file,
#'   rscript     = commandArgs(trailingOnly = TRUE)[1]
#' )
#' ```
#'
#' @export
#'
#' @examples
#' # Single-job executable script
#' htc_gen_executable(r_script = "analysis.R", output = tempdir())
#'
#' # Multiple-job executable script
#' htc_gen_executable(
#'   r_script = "analysis.R",
#'   mode     = "multiple",
#'   output   = tempdir()
#' )
#'
#' # Custom R script name with annotations
#' htc_gen_executable(
#'   output_file = "run.sh",
#'   r_script    = "run-analysis.R",
#'   comments    = TRUE,
#'   verbose     = TRUE,
#'   output      = tempdir()
#' )

htc_gen_executable <- function(output_file    = "job.sh",
                               r_script       = NULL,
                               results_folder = "results-folder",
                               mode           = "single",
                               set_executable = TRUE,
                               verbose        = FALSE,
                               comments       = FALSE,
                               output         = ".") {

    # -- 1. Validate r_script --------------------------------------------------
    if (is.null(r_script)) {
        cli::cli_abort(c(
            "{.arg r_script} must be supplied.",
            "i" = "Pass the name of the R script that HTCondor will run,",
            " " = "  e.g. {.code r_script = \"analysis.R\"}.",
            "i" = "If you used {.fn toolero::create_qmd} with",
            " " = "  {.code use_purl = TRUE}, the script is the {.code .R}",
            " " = "  file produced by {.code purl.R} after rendering."
        ))
    }

    # -- 2. Validate output_file -----------------------------------------------
    if (!grepl("\\.sh$", output_file)) {
        cli::cli_abort(c(
            "{.arg output_file} must end in {.val .sh}.",
            "i" = "Got {.val {output_file}}."
        ))
    }

    # -- 3. Validate output directory ------------------------------------------
    if (!dir.exists(output)) {
        cli::cli_abort(
            "Output directory {.path {output}} does not exist."
        )
    }

    # -- 4. Validate mode ------------------------------------------------------
    mode <- match.arg(mode, choices = c("single", "multiple"))

    # -- 5. Assemble script sections -------------------------------------------
    # Each section has a verbose_msg, an optional comment, and the line(s)
    # to write. The structure mirrors htc_gen_submit() for consistency.

    sections <- list(

        shebang = list(
            verbose_msg = "Writing shebang line",
            comment     = NULL,
            lines       = c("#!/bin/bash", "")
        ),

        permissions = list(
            verbose_msg = NULL,
            comment     = paste0(
                "# This script requires executable permissions before it can be submitted.\n",
                "# By default, htc_gen_executable() sets these permissions automatically\n",
                "# via Sys.chmod() -- so if you used set_executable = TRUE (the default),\n",
                "# you are already good to go and can copy this file directly to CHTC.\n",
                "# If you called the function with set_executable = FALSE, you must run\n",
                "#   chmod +x ", output_file, "\n",
                "# on the script before staging and submitting your job. Without this step,\n",
                "# HTCondor will fail to run the script with a 'permission denied' error."
            ),
            lines       = NULL
        ),

results_dir = list(
    verbose_msg = "Writing results folder creation",
    comment     = paste0(
        "# Create a folder to collect all output files produced by the job.\n",
        "# HTCondor transfers everything in the top-level working directory\n",
        "# back to the submit node when the job finishes. Placing outputs in\n",
        "# a subfolder keeps them organized before compression."
    ),
    lines       = glue::glue("mkdir {results_folder}")
),

execute = list(
    verbose_msg = glue::glue(
        "Writing Rscript execution line (mode: {mode})"
    ),
    comment     = if (mode == "single") {
        paste0(
            "# Run the R script using Rscript rather than R itself.\n",
            "# Rscript executes the script non-interactively with no GUI,\n",
            "# which is required for headless execution on the execute node."
        )
    } else {
        paste0(
            "# Run the R script using Rscript rather than R itself.\n",
            "# ${1} is the first positional argument passed by HTCondor --\n",
            "# the subset filename substituted from subdatasets.csv.\n",
            "# The R script receives this as commandArgs(trailingOnly = TRUE)[1].\n",
            "# Use toolero::detect_execution_context() in your R script to\n",
            "# handle this argument correctly across all execution contexts."
        )
    },
    lines       = if (mode == "single") {
        glue::glue("Rscript {r_script}")
    } else {
        glue::glue("Rscript {r_script} ${{1}}")
    }
),

compress = list(
    verbose_msg = "Writing compression line",
    comment     = if (mode == "single") {
        paste0(
            "# Compress the results folder into a single tarball.\n",
            "# This reduces the number of files transferred back to the submit\n",
            "# node and keeps results from different runs organized."
        )
    } else {
        paste0(
            "# Compress the results folder into a per-job tarball.\n",
            "# ${1} is the subset filename, giving each job a unique archive\n",
            "# name (e.g. adelie.csv-results.tar.gz) so results from different\n",
            "# jobs do not overwrite each other on the submit node."
        )
    },
    lines       = if (mode == "single") {
        glue::glue(
            "tar -czf {tools::file_path_sans_ext(r_script)}-results.tar.gz ",
            "{results_folder}"
        )
    } else {
        glue::glue("tar -czf ${{1}}-results.tar.gz {results_folder}")
    }
)
    )

    # -- 6. Write script -------------------------------------------------------
    script_path <- file.path(output, output_file)
    first <- TRUE
    for (section in sections) {
        if (is.null(section$lines) && is.null(section$comment)) next

        if (verbose && !is.null(section$verbose_msg)) {
            cli::cli_inform(section$verbose_msg)
        }
        if (comments && !is.null(section$comment)) {
            readr::write_lines(section$comment,
                               file   = script_path,
                               append = !first)
            first <- FALSE
        }
        if (!is.null(section$lines)) {
            readr::write_lines(section$lines,
                               file   = script_path,
                               append = !first)
            first <- FALSE
        }
    }

    # -- 7. Set executable permissions ----------------------------------------
    if (set_executable) {
        Sys.chmod(script_path, mode = "0755")
        if (verbose) {
            cli::cli_inform(
                "Set executable permissions on {.path {script_path}}"
            )
        }
    }

    if (verbose) {
        cli::cli_alert_success(
            "Executable script written to {.path {file.path(output, output_file)}}"
        )
    }

    invisible(NULL)
}
