//! nash — the Nucleic agent shell (see docs/NASH.md).
//!
//! A thin binary around the vendored brush fork: loads the trusted operator policy
//! (`policy`), installs the `nash-observe` recording gate (when `NUCLEIC_SHELL_*`
//! config is present, or unconditionally under a `require_observation` policy),
//! registers the exit-time event flush, applies the compat fallback for `-c` input
//! brush cannot parse, and otherwise defers entirely to brush's bash-compatible CLI.

use std::os::unix::process::CommandExt;

mod policy;

fn main() {
    // docs/NASH.md §2: let scripts/tests detect they are running under nash.
    // SAFETY: called before any other threads exist.
    unsafe { std::env::set_var("NUCLEIC_NASH", "1") };

    // Operator levers come from the trusted policy file, never from the environment
    // the observed process controls (docs/NASH.md §4.3).
    let policy = policy::Policy::load();

    // Observation is installed *before* the levers are acted on, so that a bypass
    // attempt and an operator break-glass both leave a row in the feed rather than a
    // silence. Under `require_observation` this cannot be turned off from the env.
    let observing =
        nash_observe::install_from_env(policy.require_observation, &policy.fallback_spool());
    if observing {
        brush_shell::entry::set_exit_hook(Box::new(nash_observe::flush_sync));
        for rejected in &policy.rejected {
            nash_observe::report_policy(
                &rejected.lever, &rejected.value, "ignored", &rejected.note);
        }
        if let Some(reason) = &policy.untrusted {
            nash_observe::report_policy(
                "policy-file", policy::POLICY_PATH, "ignored",
                &format!("policy file not trusted ({reason}); operator levers unavailable"));
        }
    }

    // Kill switch (docs/NASH.md §4.1): behave as the real bash, verbatim argv. Only
    // the trusted policy can set this.
    if policy.disable {
        if observing {
            nash_observe::report_policy(
                "disable", "1", "honored",
                "operator break-glass: re-execing the real bash, unobserved from here");
            nash_observe::flush_sync();
        }
        exec_real_bash(&policy);
        // exec failed; fall through and run as nash rather than break the command.
    }

    // Compat fallback (docs/NASH.md §4.1): if brush can't parse `-c` input
    // that may still be valid bash, record a fallback event and re-exec the
    // preserved real bash with identical argv so the command still runs.
    if let Some(command) = dash_c_command() {
        if let Err(parse_err) = try_parse(&command) {
            if observing {
                nash_observe::report_fallback(&format!("parse-error: {parse_err}"), &command);
            }
            exec_real_bash(&policy);
            // exec failed (no real bash available): let brush surface its own
            // parse error so the user sees a real diagnostic.
        }
    }

    brush_shell::entry::run();
}

/// Extracts the command string from a bash-style `-c` invocation, handling
/// combined short options (`-lc <cmd>`, `-elc <cmd>`, …).
///
/// bash keeps parsing short options after `-c` and takes the command from the
/// first operand, so `-c -l <cmd>` and `-c -- <cmd>` both name `<cmd>`. Skip
/// those intervening option tokens rather than mistaking one for the command
/// (which would send the compat fallback the wrong input to check).
fn dash_c_command() -> Option<String> {
    let args: Vec<String> = std::env::args().skip(1).collect();
    let mut iter = args.iter();
    while let Some(arg) = iter.next() {
        if arg == "--" {
            return None; // operands only from here on; no -c seen
        }
        if let Some(flags) = arg.strip_prefix('-') {
            if !flags.is_empty()
                && flags.chars().all(|c| c.is_ascii_alphanumeric())
                && flags.ends_with('c')
            {
                for candidate in iter.by_ref() {
                    // `--` terminates options: the very next token is the command.
                    if candidate == "--" {
                        return iter.next().cloned();
                    }
                    // Any other `-x`-shaped token is a further option, not the command.
                    if candidate.len() > 1 && candidate.starts_with('-') {
                        continue;
                    }
                    return Some(candidate.clone());
                }
                return None;
            }
        }
    }
    None
}

fn try_parse(command: &str) -> Result<(), String> {
    let reader = std::io::BufReader::new(command.as_bytes());
    let mut parser = brush_parser::Parser::new(reader, &brush_parser::ParserOptions::default());
    parser.parse_program().map(|_| ()).map_err(|e| e.to_string())
}

/// Replaces this process with the preserved real bash, keeping argv intact.
/// Returns only if every candidate exec fails.
///
/// The target comes from the trusted policy (or the compiled-in defaults) — never
/// from `NUCLEIC_REAL_BASH`, which would let the observed process choose what nash
/// re-execs into (docs/NASH.md §4.3).
fn exec_real_bash(policy: &policy::Policy) {
    let candidates = policy.real_bash_candidates();

    let self_path = std::env::current_exe().ok();
    let args: Vec<String> = std::env::args().skip(1).collect();
    for candidate in candidates {
        let path = std::path::Path::new(&candidate);
        // Never re-exec ourselves (e.g. /bin/bash diverted to nash).
        if !path.is_file()
            || self_path
                .as_deref()
                .and_then(|p| path.canonicalize().ok().map(|c| c == p))
                .unwrap_or(false)
        {
            continue;
        }
        let _ = std::process::Command::new(path).args(&args).exec();
    }
}
