//! Stubs for npm-only commands that pnpm claims at the CLI surface.
//!
//! pnpm doesn't implement `whoami`, `token`, `owner`, `search`, `pkg`, or
//! `set-script` — it parses the command name, prints a "not implemented,
//! use npm" error, and exits non-zero. We match that behavior so these
//! names don't fall through aube's implicit-script runner (`External`),
//! where `aube whoami` would otherwise try to run a `whoami` script from
//! `package.json` and produce a confusing "script not found" error.

use clap::Args;
use miette::{Context, IntoDiagnostic, bail};

#[derive(Debug, Args)]
pub struct FallbackArgs {
    /// Unused; captured so `aube <cmd> foo bar` parses instead of
    /// erroring on unexpected args before the fallback message prints.
    #[arg(trailing_var_arg = true, allow_hyphen_values = true, hide = true)]
    pub args: Vec<String>,
}

pub fn run(name: &str, args: &[String], registry: Option<&str>) -> miette::Result<i32> {
    let cwd = crate::dirs::cwd()?;
    let npmrc = aube_registry::config::load_npmrc_entries(&cwd);
    let empty_ws = std::collections::BTreeMap::new();
    let env = aube_settings::values::process_env();
    let ctx = aube_settings::ResolveCtx {
        npmrc: &npmrc,
        workspace_yaml: &empty_ws,
        env,
        cli: &[],
    };

    if let Some(npm_path) = aube_settings::resolved::npm_path(&ctx) {
        let mut cmd = std::process::Command::new(&npm_path);
        cmd.arg(name)
            .args(args)
            .stderr(aube_scripts::child_stderr());
        if let Some(registry) = registry {
            cmd.arg(format!("--registry={registry}"));
        }
        let status = cmd
            .status()
            .into_diagnostic()
            .wrap_err_with(|| format!("failed to run configured npmPath `{npm_path}`"))?;
        return Ok(child_exit_code(status));
    }

    bail!(
        "`aube {name}` is not implemented. This is an npm-only command — \
         run it with `npm {name}` instead, or set `npmPath` to let aube delegate it."
    )
}

fn child_exit_code(status: std::process::ExitStatus) -> i32 {
    if let Some(code) = status.code() {
        return code;
    }

    #[cfg(unix)]
    {
        use std::os::unix::process::ExitStatusExt;

        if let Some(signal) = status.signal() {
            return 128 + signal;
        }
    }

    1
}

#[cfg(test)]
mod tests {
    #[cfg(unix)]
    use super::child_exit_code;

    #[cfg(unix)]
    #[test]
    fn child_signal_exit_uses_shell_convention() {
        use std::os::unix::process::ExitStatusExt;

        let status = std::process::ExitStatus::from_raw(2);

        assert_eq!(child_exit_code(status), 130);
    }

    #[cfg(unix)]
    #[test]
    fn child_normal_exit_code_is_preserved() {
        use std::os::unix::process::ExitStatusExt;

        let status = std::process::ExitStatus::from_raw(7 << 8);

        assert_eq!(child_exit_code(status), 7);
    }
}
