//! Optional bundled coreutils for brush, powered by uutils/coreutils.
//!
//! Each utility is feature-gated (e.g., `coreutils.cat`, `coreutils.ls`) and
//! can be individually enabled or disabled. The `coreutils.all` feature enables
//! all cross-platform utilities.
//!
//! The crate exposes [`bundled_commands`], a registry of utility name to
//! `uumain`-style function pointer. The binary consumer (typically
//! brush-shell) installs this registry at startup; when its bundled-dispatch
//! protocol fires, the registered function is called directly. The consumer
//! is also responsible for registering any per-name builtin shims that
//! re-enter the binary as an external process, so shell redirections, pipes,
//! and process-group state are honored by uutils transparently.
//!
//! ## Relationship to `uucore::bin!`
//!
//! Each adapter generated by [`register!`] is a hand-rolled port of the body
//! of upstream's `uucore::bin!($util)` macro (SIGPIPE setup, Fluent
//! localization, `uumain` call, stdout flush). We can't invoke the macro
//! directly because it expands to a `pub fn main()` that reads argv via
//! `uucore::args_os()` and terminates the process with `std::process::exit`.
//! Our call shape is different: adapters receive argv as a parameter (the
//! `--invoke-bundled` dispatcher owns argv slicing) and must *return* an exit
//! code so the dispatcher can run a single `exit` site.
//!
//! Intentional divergences from `bin!`, all of which are benign in our usage:
//!
//! * **No `exit`:** adapters return `i32` instead of calling
//!   `std::process::exit`.
//! * **Caller-supplied argv:** `args` is passed in rather than pulled from
//!   `uucore::args_os()`.
//! * **Simpler `LocalizationError` formatting:** we render with `Display`
//!   rather than matching on `ParseResource` for snippet-aware output.

use std::collections::HashMap;
use std::ffi::OsString;

/// First half of the `uucore::bin!` body: install SIGPIPE handling and
/// initialize the thread-local Fluent localizer. Without this, `translate!`
/// calls inside the utility fall back to emitting raw Fluent keys (e.g.,
/// `ls-error-cannot-access-no-such-file`) instead of rendered messages.
///
/// `util_crate_name` is the upstream crate name (e.g., `"uu_dir"`), matching
/// what `bin!` passes via `stringify!($util)`. We route it through
/// [`uucore::get_canonical_util_name`] to apply upstream's alias map (`dir` →
/// `ls`, `vdir` → `ls`, `[` → `test`); utilities implemented as thin wrappers
/// over another crate load their translation bundle under the wrapped name,
/// so skipping this step silently breaks localization for the aliases.
///
/// Matches `uucore::bin!` behavior on localization-init failure: exit 99.
/// (This is uutils' contract, not brush's — the fact that brush-core uses 99
/// for `ExecutionExitCode::Unimplemented` is coincidental.)
/// `setup_localization` is idempotent per thread, so repeated invocations
/// (e.g., successive bundled-dispatch calls in a long-lived host, if that
/// ever happens) are cheap.
#[allow(
    dead_code,
    reason = "only called by adapters generated by the `register!` macro; with no coreutils.* features enabled, no adapter exists"
)]
fn prepare_uutil_runtime(util_crate_name: &str) {
    uucore::panic::preserve_inherited_sigpipe();
    uucore::panic::mute_sigpipe_panic();
    let localization_name = uucore::get_canonical_util_name(util_crate_name);
    if let Err(err) = uucore::locale::setup_localization(localization_name) {
        eprintln!("brush: could not initialize localization for '{localization_name}': {err}");
        std::process::exit(99);
    }
}

/// Second half of the `uucore::bin!` body: flush stdout after `uumain`
/// returns. `bin!` does this before calling `std::process::exit` to work
/// around <https://github.com/rust-lang/rust/issues/23818>. We do it here so
/// callers that don't immediately `exit` (e.g., a test harness) still observe
/// complete output.
#[allow(
    dead_code,
    reason = "only called by adapters generated by the `register!` macro; with no coreutils.* features enabled, no adapter exists"
)]
fn finalize_uutil_runtime() {
    use std::io::Write;
    if let Err(e) = std::io::stdout().flush() {
        eprintln!("brush: error flushing stdout: {e}");
    }
}

/// Adapts a uutils `uumain` (`impl Iterator<Item = OsString> -> i32`) to a
/// plain `fn(Vec<OsString>) -> i32` and inserts it into the registry under
/// the given name when its feature is enabled. The generated adapter mirrors
/// the body of `uucore::bin!`; see the module-level docs for why we port it
/// by hand rather than invoke the macro.
macro_rules! register {
    ($map:expr, $feature:literal, $name:literal, $util_crate:ident) => {
        #[cfg(feature = $feature)]
        {
            fn adapter(args: Vec<OsString>) -> i32 {
                $crate::prepare_uutil_runtime(stringify!($util_crate));
                let code = $util_crate::uumain(args.into_iter());
                $crate::finalize_uutil_runtime();
                code
            }
            $map.insert($name.to_string(), adapter as fn(Vec<OsString>) -> i32);
        }
    };
}

/// Returns the set of bundled coreutils commands enabled by feature flags.
///
/// Keyed by utility name (e.g., `"cat"`); values are `uumain`-shaped entry
/// points that consume a full argv (with `argv[0]` as the command name) and
/// return an exit code.
#[allow(clippy::too_many_lines)]
#[must_use]
pub fn bundled_commands() -> HashMap<String, fn(Vec<OsString>) -> i32> {
    #[allow(
        unused_mut,
        reason = "every register! is cfg'd on a coreutils.* feature; with none enabled, nothing mutates the map"
    )]
    let mut m = HashMap::<String, fn(Vec<OsString>) -> i32>::new();

    register!(m, "coreutils.arch", "arch", uu_arch);
    register!(m, "coreutils.base32", "base32", uu_base32);
    register!(m, "coreutils.base64", "base64", uu_base64);
    register!(m, "coreutils.basename", "basename", uu_basename);
    register!(m, "coreutils.basenc", "basenc", uu_basenc);
    register!(m, "coreutils.cat", "cat", uu_cat);
    register!(m, "coreutils.cksum", "cksum", uu_cksum);
    register!(m, "coreutils.b2sum", "b2sum", uu_b2sum);
    register!(m, "coreutils.md5sum", "md5sum", uu_md5sum);
    register!(m, "coreutils.sha1sum", "sha1sum", uu_sha1sum);
    register!(m, "coreutils.sha224sum", "sha224sum", uu_sha224sum);
    register!(m, "coreutils.sha256sum", "sha256sum", uu_sha256sum);
    register!(m, "coreutils.sha384sum", "sha384sum", uu_sha384sum);
    register!(m, "coreutils.sha512sum", "sha512sum", uu_sha512sum);
    register!(m, "coreutils.comm", "comm", uu_comm);
    register!(m, "coreutils.cp", "cp", uu_cp);
    register!(m, "coreutils.csplit", "csplit", uu_csplit);
    register!(m, "coreutils.cut", "cut", uu_cut);
    register!(m, "coreutils.date", "date", uu_date);
    register!(m, "coreutils.dd", "dd", uu_dd);
    register!(m, "coreutils.df", "df", uu_df);
    register!(m, "coreutils.dir", "dir", uu_dir);
    register!(m, "coreutils.dircolors", "dircolors", uu_dircolors);
    register!(m, "coreutils.dirname", "dirname", uu_dirname);
    register!(m, "coreutils.du", "du", uu_du);
    register!(m, "coreutils.echo", "echo", uu_echo);
    register!(m, "coreutils.env", "env", uu_env);
    register!(m, "coreutils.expand", "expand", uu_expand);
    register!(m, "coreutils.expr", "expr", uu_expr);
    register!(m, "coreutils.factor", "factor", uu_factor);
    register!(m, "coreutils.false", "false", uu_false);
    register!(m, "coreutils.fmt", "fmt", uu_fmt);
    register!(m, "coreutils.fold", "fold", uu_fold);
    register!(m, "coreutils.head", "head", uu_head);
    register!(m, "coreutils.hostname", "hostname", uu_hostname);
    register!(m, "coreutils.join", "join", uu_join);
    register!(m, "coreutils.link", "link", uu_link);
    register!(m, "coreutils.ln", "ln", uu_ln);
    register!(m, "coreutils.ls", "ls", uu_ls);
    register!(m, "coreutils.mkdir", "mkdir", uu_mkdir);
    register!(m, "coreutils.mktemp", "mktemp", uu_mktemp);
    register!(m, "coreutils.more", "more", uu_more);
    register!(m, "coreutils.mv", "mv", uu_mv);
    register!(m, "coreutils.nl", "nl", uu_nl);
    register!(m, "coreutils.nproc", "nproc", uu_nproc);
    register!(m, "coreutils.numfmt", "numfmt", uu_numfmt);
    register!(m, "coreutils.od", "od", uu_od);
    register!(m, "coreutils.paste", "paste", uu_paste);
    register!(m, "coreutils.pr", "pr", uu_pr);
    register!(m, "coreutils.printenv", "printenv", uu_printenv);
    register!(m, "coreutils.printf", "printf", uu_printf);
    register!(m, "coreutils.ptx", "ptx", uu_ptx);
    register!(m, "coreutils.pwd", "pwd", uu_pwd);
    register!(m, "coreutils.readlink", "readlink", uu_readlink);
    register!(m, "coreutils.realpath", "realpath", uu_realpath);
    register!(m, "coreutils.rm", "rm", uu_rm);
    register!(m, "coreutils.rmdir", "rmdir", uu_rmdir);
    register!(m, "coreutils.seq", "seq", uu_seq);
    register!(m, "coreutils.shred", "shred", uu_shred);
    register!(m, "coreutils.shuf", "shuf", uu_shuf);
    register!(m, "coreutils.sleep", "sleep", uu_sleep);
    register!(m, "coreutils.sort", "sort", uu_sort);
    register!(m, "coreutils.split", "split", uu_split);
    register!(m, "coreutils.sum", "sum", uu_sum);
    register!(m, "coreutils.sync", "sync", uu_sync);
    register!(m, "coreutils.tac", "tac", uu_tac);
    register!(m, "coreutils.tail", "tail", uu_tail);
    register!(m, "coreutils.tee", "tee", uu_tee);
    register!(m, "coreutils.test", "test", uu_test);
    register!(m, "coreutils.touch", "touch", uu_touch);
    register!(m, "coreutils.tr", "tr", uu_tr);
    register!(m, "coreutils.true", "true", uu_true);
    register!(m, "coreutils.truncate", "truncate", uu_truncate);
    register!(m, "coreutils.tsort", "tsort", uu_tsort);
    register!(m, "coreutils.uname", "uname", uu_uname);
    register!(m, "coreutils.unexpand", "unexpand", uu_unexpand);
    register!(m, "coreutils.uniq", "uniq", uu_uniq);
    register!(m, "coreutils.unlink", "unlink", uu_unlink);
    register!(m, "coreutils.vdir", "vdir", uu_vdir);
    register!(m, "coreutils.wc", "wc", uu_wc);
    register!(m, "coreutils.whoami", "whoami", uu_whoami);
    register!(m, "coreutils.yes", "yes", uu_yes);

    m
}
