//! naros-init — narOS PID-1 supervisor for container surfaces (docs/NAROS.md §5).
//!
//! Supervision and plumbing only, no policy:
//!  - reaps zombies (the historical job of PID 1);
//!  - forwards SIGTERM/SIGINT to supervised children;
//!  - `naros-init -- CMD ARGS…` supervises a primary command and exits with its status
//!    (129+signum on signal death), replacing `sh -c` wrappers as the container entrypoint;
//!  - with no primary command it is the keepalive that replaces ContainerEngine's
//!    sleep loop, staying alive until signalled;
//!  - NAROS_BRIDGE=1 additionally supervises the control bridge
//!    (`node /opt/nucleic/control-bridge.js`), restarting it with backoff — the bridge is
//!    best-effort transport, so its failures never affect the primary command or init.
//!
//! In the VM desktop flavor systemd stays PID 1 and this binary runs as a role unit.

use std::env;
use std::process::{exit, Command};
use std::sync::atomic::{AtomicI32, Ordering};
use std::time::{Duration, Instant};

static PENDING_SIGNAL: AtomicI32 = AtomicI32::new(0);

extern "C" fn on_signal(sig: libc::c_int) {
    PENDING_SIGNAL.store(sig, Ordering::SeqCst);
}

fn install_handlers() {
    unsafe {
        let handler = on_signal as *const () as usize;
        for sig in [libc::SIGTERM, libc::SIGINT] {
            libc::signal(sig, handler);
        }
        // SIG_DFL for SIGCHLD keeps children reapable via waitpid below.
    }
}

/// Reap every exited child; returns the primary's status when it is among them.
fn reap(primary: Option<libc::pid_t>, bridge: Option<libc::pid_t>) -> (Option<i32>, bool) {
    let mut primary_status = None;
    let mut bridge_died = false;
    loop {
        let mut status: libc::c_int = 0;
        let pid = unsafe { libc::waitpid(-1, &mut status, libc::WNOHANG) };
        if pid <= 0 {
            break;
        }
        let code = if libc::WIFEXITED(status) {
            libc::WEXITSTATUS(status)
        } else if libc::WIFSIGNALED(status) {
            128 + libc::WTERMSIG(status)
        } else {
            1
        };
        if Some(pid) == primary {
            primary_status = Some(code);
        } else if Some(pid) == bridge {
            bridge_died = true;
        }
    }
    (primary_status, bridge_died)
}

fn forward(sig: i32, pids: &[Option<libc::pid_t>]) {
    for pid in pids.iter().flatten() {
        unsafe {
            libc::kill(*pid, sig);
        }
    }
}

fn spawn_bridge() -> Option<libc::pid_t> {
    const BRIDGE: &str = "/opt/nucleic/control-bridge.js";
    if !std::path::Path::new(BRIDGE).exists() {
        return None;
    }
    match Command::new("node").arg(BRIDGE).spawn() {
        Ok(child) => Some(child.id() as libc::pid_t),
        Err(err) => {
            eprintln!("naros-init: bridge spawn failed: {err}");
            None
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.get(1).map(String::as_str) == Some("--version") {
        println!("naros-init {}", env!("CARGO_PKG_VERSION"));
        return;
    }

    install_handlers();

    let cmd: Vec<&String> = match args.iter().position(|a| a == "--") {
        Some(i) => args[i + 1..].iter().collect(),
        None => Vec::new(),
    };

    let mut primary: Option<libc::pid_t> = None;
    if let Some((prog, rest)) = cmd.split_first() {
        match Command::new(prog).args(rest).spawn() {
            Ok(child) => primary = Some(child.id() as libc::pid_t),
            Err(err) => {
                eprintln!("naros-init: exec {prog}: {err}");
                exit(127);
            }
        }
    }

    let want_bridge = env::var("NAROS_BRIDGE").ok().as_deref() == Some("1");
    let mut bridge = if want_bridge { spawn_bridge() } else { None };
    let mut bridge_backoff = Duration::from_millis(500);
    let mut bridge_retry_at: Option<Instant> = None;

    loop {
        let sig = PENDING_SIGNAL.swap(0, Ordering::SeqCst);
        if sig != 0 {
            forward(sig, &[primary, bridge]);
            if primary.is_none() {
                // Keepalive role: the signal is our own shutdown request.
                exit(128 + sig);
            }
        }

        let (primary_status, bridge_died) = reap(primary, bridge);
        if let Some(code) = primary_status {
            forward(libc::SIGTERM, &[bridge]);
            reap(None, bridge);
            exit(code);
        }
        if bridge_died {
            bridge = None;
            bridge_retry_at = Some(Instant::now() + bridge_backoff);
            bridge_backoff = (bridge_backoff * 2).min(Duration::from_secs(30));
        }
        if want_bridge && bridge.is_none() {
            if let Some(at) = bridge_retry_at {
                if Instant::now() >= at {
                    bridge = spawn_bridge();
                    bridge_retry_at = None;
                    if bridge.is_some() {
                        bridge_backoff = Duration::from_millis(500);
                    } else {
                        bridge_retry_at = Some(Instant::now() + bridge_backoff);
                    }
                }
            } else {
                bridge_retry_at = Some(Instant::now());
            }
        }

        std::thread::sleep(Duration::from_millis(100));
    }
}
