//! Lightweight POSIX shell command decomposition.
//!
//! Splits a one-line shell input on `&&`, `||`, `;`, `|` operators.
//! Quoted regions are preserved so chain tokens inside quotes do not split.
//! This is a heuristic — not a full shell parser. It is sufficient to ensure
//! a chained `rm -rf /` is not hidden behind a leading benign command.

/// Split `input` into individual sub-commands by POSIX chain operators.
/// Returns the original string as a single element if no chains are detected.
pub fn split_chain(input: &str) -> Vec<&str> {
    let mut out = Vec::new();
    let bytes = input.as_bytes();
    let mut start = 0;
    let mut i = 0;
    let mut quote: Option<u8> = None;
    let mut escape = false;

    while i < bytes.len() {
        let b = bytes[i];
        if escape { escape = false; i += 1; continue; }
        if b == b'\\' && quote != Some(b'\'') { escape = true; i += 1; continue; }

        match quote {
            Some(q) if q == b => { quote = None; i += 1; continue; }
            Some(_)            => { i += 1; continue; }
            None => {
                if b == b'\'' || b == b'"' { quote = Some(b); i += 1; continue; }
            }
        }

        let two = bytes.get(i..i+2);
        if two == Some(b"&&") || two == Some(b"||") {
            out.push(input[start..i].trim());
            i += 2; start = i; continue;
        }
        if b == b';' || b == b'|' {
            out.push(input[start..i].trim());
            i += 1; start = i; continue;
        }
        i += 1;
    }
    let tail = input[start..].trim();
    if !tail.is_empty() { out.push(tail); }
    out
}

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

    #[test] fn splits_on_and() {
        assert_eq!(split_chain("ls && rm -rf /"), vec!["ls", "rm -rf /"]);
    }
    #[test] fn splits_on_semicolon_and_pipe() {
        assert_eq!(split_chain("ls ; rm -rf / | wc"), vec!["ls", "rm -rf /", "wc"]);
    }
    #[test] fn keeps_quoted_chains_intact() {
        assert_eq!(split_chain("echo 'a && b'"), vec!["echo 'a && b'"]);
    }
    #[test] fn handles_no_chain() {
        assert_eq!(split_chain("ls -la"), vec!["ls -la"]);
    }
    #[test] fn handles_escaped_pipe() {
        assert_eq!(split_chain(r"echo a\|b"), vec![r"echo a\|b"]);
    }
}
