use std::path::PathBuf;

const VERSION: &str = env!("CARGO_PKG_VERSION");

const HELP: &str = "\
ai-jail — sandbox for AI coding agents (bwrap on Linux, sandbox-exec on macOS)

USAGE:
    ai-jail [OPTIONS] [--] [COMMAND [ARGS...]]

COMMANDS (positional):
    gemini, claude, codex, opencode, crush, soulforge, grok, bash   Known AI tool presets
    status                                 Show current .ai-jail config
    Any other string                       Passed through as the command

OPTIONS:
    --rw-map <PATH>                Mount PATH read-write inside sandbox (repeatable)
    --map <PATH>                   Mount PATH read-only inside sandbox (repeatable)
    --hide-dotdir <NAME>           Never mount dotdir NAME (e.g., .my_secrets) (repeatable)
    --mask <PATH>                  Replace PATH with an empty file inside sandbox (repeatable)
    --private-home / --no-private-home
                                   Disable/enable automatic host home dotdir passthrough
    --lockdown / --no-lockdown     Enable/disable strict read-only lockdown mode
    --landlock / --no-landlock     Enable/disable Landlock LSM (Linux 5.13+, default: on)
    --seccomp / --no-seccomp       Enable/disable seccomp syscall filter (Linux, default: on)
    --rlimits / --no-rlimits       Enable/disable resource limits (default: on)
    --no-gpu / --gpu               Disable/enable GPU device passthrough (Linux only)
    --no-docker / --docker         Disable/enable Docker socket passthrough
    --no-display / --display       Disable/enable X11/Wayland passthrough (Linux only)
    --worktree / --no-worktree     Enable/disable linked Git worktree metadata passthrough
    --no-mise / --mise             Disable/enable mise integration
    --ssh / --no-ssh               Share ~/.ssh read-only + forward SSH_AUTH_SOCK (default: off)
    --pictures / --no-pictures     Share ~/Pictures read-only (default: off)
    --browser[=PROFILE]            Use browser isolation profile (hard | soft; default hard)
    --no-browser                   Disable browser auto-detection/profile
    --save-config / --no-save-config
                                   Enable/disable automatic .ai-jail writes
    --hide-config / --no-hide-config
                                   Mask the project .ai-jail file from the agent (default: on)
    -s, --status-bar[=STYLE]       Set status line theme (pastel | dark | light; default pastel)
                                   Pastel picks a random pastel palette per session
    --no-status-bar                Disable persistent status line
    --exec                         Direct execution mode (no PTY proxy, no status bar)
    --allow-tcp-port <PORT>        Allow outbound TCP to PORT in lockdown (repeatable)
    --claude-dir <PATH>            Use PATH as Claude config dir (sets CLAUDE_CONFIG_DIR)
    --clean                        Ignore existing .ai-jail config, start fresh
    --dry-run                      Print the sandbox command without executing
    --init                         Create/update .ai-jail config and exit
    --bootstrap                    Generate smart permission configs for AI tools
    -v, --verbose                  Show detailed mount info
    -h, --help                     Show help
    -V, --version                  Show version
";

#[derive(Debug, Default)]
pub struct CliArgs {
    pub command: Vec<String>,
    pub rw_maps: Vec<PathBuf>,
    pub ro_maps: Vec<PathBuf>,
    pub hide_dotdirs: Vec<String>,
    pub mask: Vec<PathBuf>,
    pub private_home: Option<bool>,
    pub lockdown: Option<bool>,
    pub landlock: Option<bool>,
    pub seccomp: Option<bool>,
    pub rlimits: Option<bool>,
    pub gpu: Option<bool>,
    pub docker: Option<bool>,
    pub display: Option<bool>,
    pub worktree: Option<bool>,
    pub mise: Option<bool>,
    pub save_config: Option<bool>,
    pub hide_config: Option<bool>,
    pub ssh: Option<bool>,
    pub pictures: Option<bool>,
    pub browser_profile: Option<String>,
    pub status_bar: Option<bool>,
    pub status_bar_style: Option<String>,
    pub allow_tcp_ports: Vec<u16>,
    pub claude_dir: Option<PathBuf>,
    pub exec: bool,
    pub clean: bool,
    pub dry_run: bool,
    pub init: bool,
    pub bootstrap: bool,
    pub verbose: bool,
    pub status: bool,
    /// Internal: apply Landlock and exec remaining command.
    /// Used as a wrapper inside the bwrap sandbox.
    pub landlock_exec: bool,
}

pub fn parse() -> Result<CliArgs, String> {
    parse_from(lexopt::Parser::from_env())
}

pub fn parse_from(mut parser: lexopt::Parser) -> Result<CliArgs, String> {
    use lexopt::prelude::*;

    let mut args = CliArgs::default();

    while let Some(arg) = parser.next().map_err(|e| e.to_string())? {
        match arg {
            Long("rw-map") => {
                let val: PathBuf =
                    parser.value().map_err(|e| e.to_string())?.into();
                args.rw_maps.push(val);
            }
            Long("map") => {
                let val: PathBuf =
                    parser.value().map_err(|e| e.to_string())?.into();
                args.ro_maps.push(val);
            }
            Long("mask") => {
                let val = parser.value().map_err(|e| e.to_string())?;
                let s = val.to_string_lossy();
                if s.is_empty() {
                    return Err("--mask requires a non-empty path".into());
                }
                args.mask.push(PathBuf::from(s.into_owned()));
            }
            Long("hide-dotdir") => {
                let val = parser.value().map_err(|e| e.to_string())?;
                let s = val.to_string_lossy().into_owned();
                if s.is_empty() {
                    return Err(
                        "--hide-dotdir requires a non-empty value".into()
                    );
                }
                let normalized = if s.starts_with('.') {
                    s
                } else {
                    format!(".{s}")
                };
                args.hide_dotdirs.push(normalized);
            }
            Long(s @ ("lockdown" | "no-lockdown")) => {
                args.lockdown = Some(s == "lockdown");
            }
            Long(s @ ("private-home" | "no-private-home")) => {
                args.private_home = Some(s == "private-home");
            }
            Long(s @ ("landlock" | "no-landlock")) => {
                args.landlock = Some(s == "landlock");
            }
            Long(s @ ("seccomp" | "no-seccomp")) => {
                args.seccomp = Some(s == "seccomp");
            }
            Long(s @ ("rlimits" | "no-rlimits")) => {
                args.rlimits = Some(s == "rlimits");
            }
            Long("allow-tcp-port") => {
                let val: String = parser
                    .value()
                    .map_err(|e| e.to_string())?
                    .to_string_lossy()
                    .into_owned();
                let port: u16 = val
                    .parse()
                    .map_err(|_| format!("invalid port number: {val}"))?;
                args.allow_tcp_ports.push(port);
            }
            Long("claude-dir") => {
                let val = parser.value().map_err(|e| e.to_string())?;
                args.claude_dir =
                    Some(PathBuf::from(val.to_string_lossy().into_owned()));
            }
            Long(s @ ("gpu" | "no-gpu")) => {
                args.gpu = Some(s == "gpu");
            }
            Long(s @ ("docker" | "no-docker")) => {
                args.docker = Some(s == "docker");
            }
            Long(s @ ("display" | "no-display")) => {
                args.display = Some(s == "display");
            }
            Long(s @ ("worktree" | "no-worktree")) => {
                args.worktree = Some(s == "worktree");
            }
            Long(s @ ("mise" | "no-mise")) => {
                args.mise = Some(s == "mise");
            }
            Long(s @ ("save-config" | "no-save-config")) => {
                args.save_config = Some(s == "save-config");
            }
            Long(s @ ("hide-config" | "no-hide-config")) => {
                args.hide_config = Some(s == "hide-config");
            }
            Long(s @ ("ssh" | "no-ssh")) => {
                args.ssh = Some(s == "ssh");
            }
            Long(s @ ("pictures" | "no-pictures")) => {
                args.pictures = Some(s == "pictures");
            }
            Long("browser") => {
                let profile = if let Some(val) = parser.optional_value() {
                    let s = val.to_string_lossy();
                    match s.as_ref() {
                        "hard" | "soft" => s.into_owned(),
                        _ => {
                            return Err(format!(
                                "invalid browser profile: \
                                 {s} (expected 'hard' or 'soft')"
                            ));
                        }
                    }
                } else {
                    "hard".into()
                };
                args.browser_profile = Some(profile);
            }
            Long("no-browser") => args.browser_profile = Some("off".into()),
            Long("status-bar") | Short('s') => {
                if let Some(val) = parser.optional_value() {
                    let s = val.to_string_lossy();
                    match s.as_ref() {
                        "dark" | "light" | "pastel" => {
                            args.status_bar_style = Some(s.into_owned());
                        }
                        _ => {
                            return Err(format!(
                                "invalid status bar style: \
                                 {s} (expected 'dark', 'light', or 'pastel')"
                            ));
                        }
                    }
                } else {
                    args.status_bar_style = Some("pastel".into());
                }
            }
            Long("no-status-bar") => args.status_bar = Some(false),
            Long("exec") => {
                args.exec = true;
                args.status_bar = Some(false);
            }
            Long("landlock-exec") => args.landlock_exec = true,
            Long("clean") => args.clean = true,
            Long("dry-run") => args.dry_run = true,
            Long("init") => args.init = true,
            Long("bootstrap") => args.bootstrap = true,
            Short('v') | Long("verbose") => args.verbose = true,
            Short('h') | Long("help") => {
                print!("{HELP}");
                std::process::exit(0);
            }
            Short('V') | Long("version") => {
                println!("ai-jail {VERSION}");
                std::process::exit(0);
            }
            Value(val) => {
                let s = val.to_string_lossy().into_owned();
                if s == "status" {
                    args.status = true;
                } else {
                    args.command.push(s);
                    // Consume ALL remaining args as part of the command
                    // (including --flags that belong to the sub-command)
                    for raw in parser.raw_args().map_err(|e| e.to_string())? {
                        args.command.push(raw.to_string_lossy().into_owned());
                    }
                }
            }
            Long(other) => return Err(format!("unknown option: --{other}")),
            Short(c) => return Err(format!("unknown option: -{c}")),
        }
    }

    Ok(args)
}

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

    fn parse_test(args: &[&str]) -> Result<CliArgs, String> {
        let parser = lexopt::Parser::from_args(args);
        parse_from(parser)
    }

    // ── Basic command parsing ──────────────────────────────────

    #[test]
    fn parse_no_args() {
        let args = parse_test(&[]).unwrap();
        assert!(args.command.is_empty());
        assert_eq!(args.lockdown, None);
        assert!(!args.dry_run);
        assert!(!args.init);
        assert!(!args.verbose);
        assert!(!args.clean);
        assert!(!args.status);
    }

    #[test]
    fn parse_simple_command() {
        let args = parse_test(&["claude"]).unwrap();
        assert_eq!(args.command, vec!["claude"]);
    }

    #[test]
    fn parse_command_with_args() {
        let args = parse_test(&["claude", "--model", "opus"]).unwrap();
        assert_eq!(args.command, vec!["claude", "--model", "opus"]);
    }

    #[test]
    fn parse_bash_command() {
        let args = parse_test(&["bash"]).unwrap();
        assert_eq!(args.command, vec!["bash"]);
    }

    #[test]
    fn parse_status_command() {
        let args = parse_test(&["status"]).unwrap();
        assert!(args.status);
        assert!(args.command.is_empty());
    }

    // ── Flag parsing ───────────────────────────────────────────

    #[test]
    fn parse_dry_run() {
        let args = parse_test(&["--dry-run", "bash"]).unwrap();
        assert!(args.dry_run);
        assert_eq!(args.command, vec!["bash"]);
    }

    #[test]
    fn parse_init() {
        let args = parse_test(&["--init", "claude"]).unwrap();
        assert!(args.init);
        assert_eq!(args.command, vec!["claude"]);
    }

    #[test]
    fn parse_clean() {
        let args = parse_test(&["--clean", "bash"]).unwrap();
        assert!(args.clean);
    }

    #[test]
    fn parse_verbose_short() {
        let args = parse_test(&["-v", "bash"]).unwrap();
        assert!(args.verbose);
    }

    #[test]
    fn parse_verbose_long() {
        let args = parse_test(&["--verbose", "bash"]).unwrap();
        assert!(args.verbose);
    }

    // ── Boolean toggle flags ───────────────────────────────────

    #[test]
    fn parse_no_gpu() {
        let args = parse_test(&["--no-gpu", "bash"]).unwrap();
        assert_eq!(args.gpu, Some(false));
    }

    #[test]
    fn parse_lockdown() {
        let args = parse_test(&["--lockdown", "bash"]).unwrap();
        assert_eq!(args.lockdown, Some(true));
    }

    #[test]
    fn parse_private_home() {
        let args = parse_test(&["--private-home", "bash"]).unwrap();
        assert_eq!(args.private_home, Some(true));
    }

    #[test]
    fn parse_no_private_home() {
        let args = parse_test(&["--no-private-home", "bash"]).unwrap();
        assert_eq!(args.private_home, Some(false));
    }

    #[test]
    fn parse_no_lockdown() {
        let args = parse_test(&["--no-lockdown", "bash"]).unwrap();
        assert_eq!(args.lockdown, Some(false));
    }

    #[test]
    fn parse_landlock() {
        let args = parse_test(&["--landlock", "bash"]).unwrap();
        assert_eq!(args.landlock, Some(true));
    }

    #[test]
    fn parse_no_landlock() {
        let args = parse_test(&["--no-landlock", "bash"]).unwrap();
        assert_eq!(args.landlock, Some(false));
    }

    #[test]
    fn parse_gpu() {
        let args = parse_test(&["--gpu", "bash"]).unwrap();
        assert_eq!(args.gpu, Some(true));
    }

    #[test]
    fn parse_no_docker() {
        let args = parse_test(&["--no-docker", "bash"]).unwrap();
        assert_eq!(args.docker, Some(false));
    }

    #[test]
    fn parse_docker() {
        let args = parse_test(&["--docker", "bash"]).unwrap();
        assert_eq!(args.docker, Some(true));
    }

    #[test]
    fn parse_no_display() {
        let args = parse_test(&["--no-display", "bash"]).unwrap();
        assert_eq!(args.display, Some(false));
    }

    #[test]
    fn parse_display() {
        let args = parse_test(&["--display", "bash"]).unwrap();
        assert_eq!(args.display, Some(true));
    }

    #[test]
    fn parse_no_mise() {
        let args = parse_test(&["--no-mise", "bash"]).unwrap();
        assert_eq!(args.mise, Some(false));
    }

    #[test]
    fn parse_worktree() {
        let args = parse_test(&["--worktree", "bash"]).unwrap();
        assert_eq!(args.worktree, Some(true));
    }

    #[test]
    fn parse_no_worktree() {
        let args = parse_test(&["--no-worktree", "bash"]).unwrap();
        assert_eq!(args.worktree, Some(false));
    }

    #[test]
    fn parse_mise() {
        let args = parse_test(&["--mise", "bash"]).unwrap();
        assert_eq!(args.mise, Some(true));
    }

    #[test]
    fn parse_ssh() {
        let args = parse_test(&["--ssh", "bash"]).unwrap();
        assert_eq!(args.ssh, Some(true));
    }

    #[test]
    fn parse_no_ssh() {
        let args = parse_test(&["--no-ssh", "bash"]).unwrap();
        assert_eq!(args.ssh, Some(false));
    }

    #[test]
    fn parse_pictures() {
        let args = parse_test(&["--pictures", "bash"]).unwrap();
        assert_eq!(args.pictures, Some(true));
    }

    #[test]
    fn parse_no_pictures() {
        let args = parse_test(&["--no-pictures", "bash"]).unwrap();
        assert_eq!(args.pictures, Some(false));
    }

    #[test]
    fn parse_browser_default_hard() {
        let args = parse_test(&["--browser", "chromium"]).unwrap();
        assert_eq!(args.browser_profile.as_deref(), Some("hard"));
        assert_eq!(args.command, vec!["chromium"]);
    }

    #[test]
    fn parse_browser_soft() {
        let args = parse_test(&["--browser=soft", "firefox"]).unwrap();
        assert_eq!(args.browser_profile.as_deref(), Some("soft"));
        assert_eq!(args.command, vec!["firefox"]);
    }

    #[test]
    fn parse_no_browser() {
        let args = parse_test(&["--no-browser", "chromium"]).unwrap();
        assert_eq!(args.browser_profile.as_deref(), Some("off"));
    }

    #[test]
    fn parse_browser_invalid_errors() {
        let result = parse_test(&["--browser=maybe", "chromium"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_save_config() {
        let args = parse_test(&["--save-config", "bash"]).unwrap();
        assert_eq!(args.save_config, Some(true));
    }

    #[test]
    fn parse_no_save_config() {
        let args = parse_test(&["--no-save-config", "bash"]).unwrap();
        assert_eq!(args.save_config, Some(false));
    }

    #[test]
    fn parse_hide_config() {
        let args = parse_test(&["--hide-config", "bash"]).unwrap();
        assert_eq!(args.hide_config, Some(true));
    }

    #[test]
    fn parse_no_hide_config() {
        let args = parse_test(&["--no-hide-config", "bash"]).unwrap();
        assert_eq!(args.hide_config, Some(false));
    }

    // ── Map flags ──────────────────────────────────────────────

    #[test]
    fn parse_mask_single() {
        let args = parse_test(&["--mask", ".env", "bash"]).unwrap();
        assert_eq!(args.mask, vec![PathBuf::from(".env")]);
    }

    #[test]
    fn parse_mask_multiple() {
        let args =
            parse_test(&["--mask", ".env", "--mask", ".env.local", "bash"])
                .unwrap();
        assert_eq!(
            args.mask,
            vec![PathBuf::from(".env"), PathBuf::from(".env.local")]
        );
    }

    #[test]
    fn parse_mask_empty_errors() {
        let result = parse_test(&["--mask", "", "bash"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_mask_missing_value_errors() {
        let result = parse_test(&["--mask"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_rw_map() {
        let args = parse_test(&["--rw-map", "/tmp/test", "bash"]).unwrap();
        assert_eq!(args.rw_maps, vec![PathBuf::from("/tmp/test")]);
    }

    #[test]
    fn parse_ro_map() {
        let args = parse_test(&["--map", "/opt/data", "bash"]).unwrap();
        assert_eq!(args.ro_maps, vec![PathBuf::from("/opt/data")]);
    }

    #[test]
    fn parse_multiple_maps() {
        let args = parse_test(&[
            "--rw-map", "/tmp/a", "--rw-map", "/tmp/b", "--map", "/opt/c",
            "bash",
        ])
        .unwrap();
        assert_eq!(
            args.rw_maps,
            vec![PathBuf::from("/tmp/a"), PathBuf::from("/tmp/b")]
        );
        assert_eq!(args.ro_maps, vec![PathBuf::from("/opt/c")]);
    }

    // ── Hide dotdir tests ────────────────────────────────────────

    #[test]
    fn parse_hide_dotdir() {
        let args =
            parse_test(&["--hide-dotdir", ".my_secrets", "bash"]).unwrap();
        assert_eq!(args.hide_dotdirs, vec![".my_secrets"]);
    }

    #[test]
    fn parse_multiple_hide_dotdirs() {
        let args = parse_test(&[
            "--hide-dotdir",
            ".my_secrets",
            "--hide-dotdir",
            ".proton",
            "bash",
        ])
        .unwrap();
        assert_eq!(args.hide_dotdirs, vec![".my_secrets", ".proton"]);
    }

    #[test]
    fn parse_hide_dotdir_with_maps() {
        let args = parse_test(&[
            "--hide-dotdir",
            ".aws",
            "--rw-map",
            "/tmp/test",
            "--map",
            "/opt/data",
            "bash",
        ])
        .unwrap();
        assert_eq!(args.hide_dotdirs, vec![".aws"]);
        assert_eq!(args.rw_maps, vec![PathBuf::from("/tmp/test")]);
        assert_eq!(args.ro_maps, vec![PathBuf::from("/opt/data")]);
    }

    #[test]
    fn parse_hide_dotdir_normalizes_no_dot() {
        let args =
            parse_test(&["--hide-dotdir", "my_secrets", "bash"]).unwrap();
        assert_eq!(args.hide_dotdirs, vec![".my_secrets"]);
    }

    #[test]
    fn parse_hide_dotdir_keeps_existing_dot() {
        let args =
            parse_test(&["--hide-dotdir", ".my_secrets", "bash"]).unwrap();
        assert_eq!(args.hide_dotdirs, vec![".my_secrets"]);
    }

    #[test]
    fn parse_hide_dotdir_empty_errors() {
        let result = parse_test(&["--hide-dotdir", "", "bash"]);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("non-empty"));
    }

    // ── Combined flags ─────────────────────────────────────────

    #[test]
    fn parse_multiple_flags_combined() {
        let args = parse_test(&[
            "--dry-run",
            "--verbose",
            "--no-gpu",
            "--no-docker",
            "--worktree",
            "--rw-map",
            "/tmp/test",
            "claude",
        ])
        .unwrap();
        assert!(args.dry_run);
        assert!(args.verbose);
        assert_eq!(args.gpu, Some(false));
        assert_eq!(args.docker, Some(false));
        assert_eq!(args.worktree, Some(true));
        assert_eq!(args.rw_maps, vec![PathBuf::from("/tmp/test")]);
        assert_eq!(args.command, vec!["claude"]);
    }

    #[test]
    fn parse_init_clean_together() {
        let args = parse_test(&["--clean", "--init", "bash"]).unwrap();
        assert!(args.clean);
        assert!(args.init);
        assert_eq!(args.command, vec!["bash"]);
    }

    // ── Error cases ────────────────────────────────────────────

    #[test]
    fn parse_bootstrap() {
        let args = parse_test(&["--bootstrap"]).unwrap();
        assert!(args.bootstrap);
        assert!(args.command.is_empty());
    }

    #[test]
    fn parse_bootstrap_with_verbose() {
        let args = parse_test(&["--bootstrap", "-v"]).unwrap();
        assert!(args.bootstrap);
        assert!(args.verbose);
    }

    // ── Error cases ────────────────────────────────────────────

    #[test]
    fn parse_unknown_flag_errors() {
        let result = parse_test(&["--unknown-flag"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_unknown_short_flag_errors() {
        let result = parse_test(&["-z"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_rw_map_missing_value_errors() {
        let result = parse_test(&["--rw-map"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_hide_dotdir_missing_value_errors() {
        let result = parse_test(&["--hide-dotdir"]);
        assert!(result.is_err());
    }

    // ── Internal flags ─────────────────────────────────────────

    #[test]
    fn parse_landlock_exec() {
        let args =
            parse_test(&["--landlock-exec", "--", "claude", "--continue"])
                .unwrap();
        assert!(args.landlock_exec);
        assert_eq!(args.command, vec!["claude", "--continue"]);
    }

    #[test]
    fn parse_landlock_exec_with_lockdown() {
        let args = parse_test(&[
            "--landlock-exec",
            "--lockdown",
            "--verbose",
            "--",
            "bash",
        ])
        .unwrap();
        assert!(args.landlock_exec);
        assert_eq!(args.lockdown, Some(true));
        assert!(args.verbose);
        assert_eq!(args.command, vec!["bash"]);
    }

    // ── Exec mode ──────────────────────────────────────────────

    #[test]
    fn parse_exec_simple() {
        let args = parse_test(&["--exec", "my-script.sh"]).unwrap();
        assert!(args.exec);
        assert_eq!(args.status_bar, Some(false));
        assert_eq!(args.command, vec!["my-script.sh"]);
    }

    #[test]
    fn parse_exec_with_args() {
        let args = parse_test(&[
            "--exec",
            "--",
            "my-script.sh",
            "--flag",
            "-o",
            "out",
        ])
        .unwrap();
        assert!(args.exec);
        assert_eq!(args.command, vec!["my-script.sh", "--flag", "-o", "out"]);
    }

    #[test]
    fn parse_exec_with_sandbox_flags() {
        let args = parse_test(&["--lockdown", "--exec", "--", "cargo", "test"])
            .unwrap();
        assert!(args.exec);
        assert_eq!(args.lockdown, Some(true));
        assert_eq!(args.command, vec!["cargo", "test"]);
    }

    // ── Dash-dash separator ────────────────────────────────────

    #[test]
    fn parse_dashdash_passes_remaining_as_command() {
        let args =
            parse_test(&["--dry-run", "--", "my-tool", "--some-flag"]).unwrap();
        assert!(args.dry_run);
        assert_eq!(args.command, vec!["my-tool", "--some-flag"]);
    }

    // ── Last-wins behavior for toggles ─────────────────────────

    #[test]
    fn parse_status_bar() {
        let args = parse_test(&["--status-bar", "bash"]).unwrap();
        assert_eq!(args.status_bar, None);
        assert_eq!(args.status_bar_style.as_deref(), Some("pastel"));
    }

    #[test]
    fn parse_no_status_bar() {
        let args = parse_test(&["--no-status-bar", "bash"]).unwrap();
        assert_eq!(args.status_bar, Some(false));
    }

    #[test]
    fn parse_status_bar_short() {
        let args = parse_test(&["-s", "bash"]).unwrap();
        assert_eq!(args.status_bar, None);
        assert_eq!(args.status_bar_style.as_deref(), Some("pastel"));
    }

    #[test]
    fn parse_status_bar_eq_light() {
        let args = parse_test(&["--status-bar=light", "bash"]).unwrap();
        assert_eq!(args.status_bar, None);
        assert_eq!(args.status_bar_style.as_deref(), Some("light"));
    }

    #[test]
    fn parse_status_bar_eq_dark() {
        let args = parse_test(&["--status-bar=dark", "bash"]).unwrap();
        assert_eq!(args.status_bar, None);
        assert_eq!(args.status_bar_style.as_deref(), Some("dark"));
    }

    #[test]
    fn parse_status_bar_eq_invalid() {
        let result = parse_test(&["--status-bar=neon", "bash"]);
        assert!(result.is_err());
    }

    #[test]
    fn parse_allow_tcp_port_single() {
        let args =
            parse_test(&["--lockdown", "--allow-tcp-port", "32000", "bash"])
                .unwrap();
        assert_eq!(args.allow_tcp_ports, vec![32000]);
        assert_eq!(args.lockdown, Some(true));
    }

    #[test]
    fn parse_allow_tcp_port_multiple() {
        let args = parse_test(&[
            "--allow-tcp-port",
            "32000",
            "--allow-tcp-port",
            "8080",
            "bash",
        ])
        .unwrap();
        assert_eq!(args.allow_tcp_ports, vec![32000, 8080]);
    }

    #[test]
    fn parse_allow_tcp_port_boundary_values() {
        let args = parse_test(&[
            "--allow-tcp-port",
            "0",
            "--allow-tcp-port",
            "65535",
            "bash",
        ])
        .unwrap();
        assert_eq!(args.allow_tcp_ports, vec![0, 65535]);
    }

    #[test]
    fn parse_allow_tcp_port_overflow() {
        assert!(parse_test(&["--allow-tcp-port", "65536"]).is_err());
    }

    #[test]
    fn parse_allow_tcp_port_invalid() {
        assert!(parse_test(&["--allow-tcp-port", "abc"]).is_err());
    }

    #[test]
    fn parse_allow_tcp_port_missing_value() {
        assert!(parse_test(&["--allow-tcp-port"]).is_err());
    }

    #[test]
    fn parse_claude_dir() {
        let args = parse_test(&[
            "--claude-dir",
            "/home/user/.claude-example",
            "claude",
        ])
        .unwrap();
        assert_eq!(
            args.claude_dir,
            Some(PathBuf::from("/home/user/.claude-example"))
        );
        assert_eq!(args.command, vec!["claude"]);
    }

    #[test]
    fn parse_claude_dir_missing_value_errors() {
        assert!(parse_test(&["--claude-dir"]).is_err());
    }

    #[test]
    fn parse_last_wins_gpu() {
        let args = parse_test(&["--no-gpu", "--gpu", "bash"]).unwrap();
        assert_eq!(args.gpu, Some(true));
    }

    #[test]
    fn parse_last_wins_docker() {
        let args = parse_test(&["--docker", "--no-docker", "bash"]).unwrap();
        assert_eq!(args.docker, Some(false));
    }

    #[test]
    fn parse_last_wins_save_config_enabled() {
        let args =
            parse_test(&["--no-save-config", "--save-config", "bash"]).unwrap();
        assert_eq!(args.save_config, Some(true));
    }

    #[test]
    fn parse_last_wins_save_config_disabled() {
        let args =
            parse_test(&["--save-config", "--no-save-config", "bash"]).unwrap();
        assert_eq!(args.save_config, Some(false));
    }
}
