use base64::{engine::general_purpose::STANDARD, Engine};
use serde_json::{json, Value};
use std::io::{self, BufRead};

use crate::color;
use crate::flags::Flags;
use crate::validation::{is_valid_session_name, session_name_error};

/// Error type for command parsing with contextual information
#[derive(Debug)]
pub enum ParseError {
    /// Command does not exist
    UnknownCommand { command: String },
    /// Command exists but subcommand is invalid
    UnknownSubcommand {
        subcommand: String,
        valid_options: &'static [&'static str],
    },
    /// Command/subcommand exists but required arguments are missing
    MissingArguments {
        context: String,
        usage: &'static str,
    },
    /// Argument exists but has an invalid value
    InvalidValue {
        message: String,
        usage: &'static str,
    },
    /// Invalid session name (path traversal or invalid characters)
    InvalidSessionName { name: String },
}

impl ParseError {
    pub fn format(&self) -> String {
        match self {
            ParseError::UnknownCommand { command } => {
                format!("Unknown command: {}", command)
            }
            ParseError::UnknownSubcommand {
                subcommand,
                valid_options,
            } => {
                format!(
                    "Unknown subcommand: {}\nValid options: {}",
                    subcommand,
                    valid_options.join(", ")
                )
            }
            ParseError::MissingArguments { context, usage } => {
                format!(
                    "Missing arguments for: {}\nUsage: agent-browser {}",
                    context, usage
                )
            }
            ParseError::InvalidValue { message, usage } => {
                format!("{}\nUsage: agent-browser {}", message, usage)
            }
            ParseError::InvalidSessionName { name } => session_name_error(name),
        }
    }
}

pub fn gen_id() -> String {
    format!(
        "r{}",
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_micros()
            % 1000000
    )
}

/// Parse a cookies file in one of three auto-detected formats:
///
/// 1. JSON array — `[{"name":"x","value":"y"}, ...]`
/// 2. cURL dump  — the output of DevTools → Network → Copy → Copy as cURL
///    (the Cookie header is extracted from `-H 'cookie: ...'` or
///    `-b '...'`/`--cookie '...'`)
/// 3. Bare cookie header — `name=value; name2=value2`
///
/// Returns a JSON array of cookie objects (each `{ name, value }`) suitable
/// for the `cookies_set` daemon action. Error text never echoes the secret
/// value.
pub fn parse_curl_cookies(raw: &str) -> Result<Vec<Value>, String> {
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return Err("cookies file is empty".to_string());
    }

    if trimmed.starts_with('[') {
        let arr: Vec<Value> = serde_json::from_str(trimmed)
            .map_err(|e| format!("cookies JSON parse error: {}", e))?;
        let mut out = Vec::with_capacity(arr.len());
        for (i, c) in arr.into_iter().enumerate() {
            let name = c
                .get("name")
                .and_then(|v| v.as_str())
                .ok_or_else(|| format!("cookies[{}] missing string name", i))?;
            let value = c
                .get("value")
                .and_then(|v| v.as_str())
                .ok_or_else(|| format!("cookies[{}] missing string value", i))?;
            let mut cookie = json!({ "name": name, "value": value });
            let obj = cookie.as_object_mut().unwrap();
            // Preserve any CDP Network.setCookie attributes present on the
            // source object so a full auth state round-trips: httpOnly session
            // tokens, per-domain cookies (a single export spans .chatgpt.com,
            // .openai.com, ...), and secure/sameSite/expiry. A bare
            // {name,value} export is unchanged. Common aliases from DevTools /
            // EditThisCookie / extension exports are accepted.
            if let Some(v) = c.get("url").and_then(|v| v.as_str()) {
                obj.insert("url".into(), json!(v));
            }
            if let Some(v) = c.get("domain").and_then(|v| v.as_str()) {
                obj.insert("domain".into(), json!(v));
            }
            if let Some(v) = c.get("path").and_then(|v| v.as_str()) {
                obj.insert("path".into(), json!(v));
            }
            if let Some(v) = c.get("secure").and_then(|v| v.as_bool()) {
                obj.insert("secure".into(), json!(v));
            }
            if let Some(v) = c
                .get("httpOnly")
                .or_else(|| c.get("httponly"))
                .or_else(|| c.get("http_only"))
                .and_then(|v| v.as_bool())
            {
                obj.insert("httpOnly".into(), json!(v));
            }
            if let Some(v) = c
                .get("sameSite")
                .or_else(|| c.get("samesite"))
                .or_else(|| c.get("same_site"))
                .and_then(|v| v.as_str())
            {
                let norm = match v.to_lowercase().as_str() {
                    "strict" => "Strict",
                    "lax" => "Lax",
                    "none" | "no_restriction" => "None",
                    _ => "",
                };
                if !norm.is_empty() {
                    obj.insert("sameSite".into(), json!(norm));
                }
            }
            // CDP `expires` is seconds since the Unix epoch (f64). Accept
            // `expires` or EditThisCookie's `expirationDate`.
            if let Some(v) = c
                .get("expires")
                .or_else(|| c.get("expirationDate"))
                .and_then(|v| v.as_f64())
            {
                if v > 0.0 {
                    obj.insert("expires".into(), json!(v));
                }
            }
            out.push(cookie);
        }
        return Ok(out);
    }

    // Heuristic: cURL commands start with `curl` followed by space/quote.
    let looks_like_curl = {
        let head: String = trimmed.chars().take(5).collect::<String>().to_lowercase();
        head.starts_with("curl") && head.len() > 4 && {
            let c = head.chars().nth(4).unwrap();
            c.is_whitespace() || c == '\'' || c == '"'
        }
    };

    let header = if looks_like_curl {
        extract_cookie_header_from_curl(trimmed).ok_or_else(|| {
            "no Cookie header found in this cURL - right-click an authenticated request in DevTools → Network → Copy → Copy as cURL".to_string()
        })?
    } else {
        trimmed.to_string()
    };

    parse_cookie_header(&header)
}

fn extract_cookie_header_from_curl(curl: &str) -> Option<String> {
    // Strip bash (`\`) and cmd (`^`) line continuations so -H is on one line.
    let joined = curl
        .replace("\\\r\n", " ")
        .replace("\\\n", " ")
        .replace("^\r\n", " ")
        .replace("^\n", " ");
    if let Some(v) = match_quoted_arg(&joined, "-H", Some("cookie")) {
        return Some(v);
    }
    if let Some(v) = match_quoted_arg(&joined, "-b", None) {
        return Some(v);
    }
    if let Some(v) = match_quoted_arg(&joined, "--cookie", None) {
        return Some(v);
    }
    None
}

/// Find `flag <quote>[header:]value<quote>` in haystack and return the value.
/// When `expect_header` is set, the quoted value must start with that header
/// name followed by a colon (case-insensitive) and the prefix is stripped.
fn match_quoted_arg(haystack: &str, flag: &str, expect_header: Option<&str>) -> Option<String> {
    let bytes = haystack.as_bytes();
    let flag_b = flag.as_bytes();
    let mut i = 0;
    while i + flag_b.len() < bytes.len() {
        if &bytes[i..i + flag_b.len()] != flag_b {
            i += 1;
            continue;
        }
        // Must be at a word boundary on the left (start of string or whitespace).
        if i > 0 && !bytes[i - 1].is_ascii_whitespace() {
            i += 1;
            continue;
        }
        let mut j = i + flag_b.len();
        // Require a whitespace separator after the flag.
        if j >= bytes.len() || !bytes[j].is_ascii_whitespace() {
            i += 1;
            continue;
        }
        while j < bytes.len() && bytes[j].is_ascii_whitespace() {
            j += 1;
        }
        if j >= bytes.len() {
            return None;
        }
        let quote = bytes[j];
        if quote != b'\'' && quote != b'"' {
            i = j;
            continue;
        }
        let start = j + 1;
        let mut k = start;
        while k < bytes.len() && bytes[k] != quote {
            k += 1;
        }
        if k >= bytes.len() {
            return None;
        }
        let value = String::from_utf8_lossy(&bytes[start..k]).into_owned();
        if let Some(header) = expect_header {
            let lower = value.to_lowercase();
            let prefix = format!("{}:", header.to_lowercase());
            if let Some(stripped) = lower.strip_prefix(&prefix) {
                let _ = stripped;
                return Some(value[prefix.len()..].trim().to_string());
            }
            i = k + 1;
            continue;
        }
        return Some(value);
    }
    None
}

fn parse_cookie_header(header: &str) -> Result<Vec<Value>, String> {
    let mut out = Vec::new();
    for piece in header.split(';') {
        let piece = piece.trim();
        let Some(eq) = piece.find('=') else { continue };
        let name = piece[..eq].trim();
        let value = piece[eq + 1..].trim();
        if !name.is_empty() {
            out.push(json!({ "name": name, "value": value }));
        }
    }
    if out.is_empty() {
        return Err("no cookies found in input".to_string());
    }
    Ok(out)
}

pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError> {
    let mut result = parse_command_inner(args, flags)?;

    // Inject AGENT_BROWSER_DEFAULT_TIMEOUT into any wait-family command that
    // doesn't already carry an explicit timeout. Centralised here so that new
    // wait variants automatically inherit the default without per-variant wiring.
    if let Some(action) = result.get("action").and_then(|a| a.as_str()) {
        if action.starts_with("wait") && result.get("timeout").is_none() {
            if let Some(t) = flags.default_timeout {
                result["timeout"] = json!(t);
            }
        }
    }

    Ok(result)
}

fn parse_command_inner(args: &[String], flags: &Flags) -> Result<Value, ParseError> {
    if args.is_empty() {
        return Err(ParseError::MissingArguments {
            context: "".to_string(),
            usage: "<command> [args...]",
        });
    }

    let cmd = args[0].as_str();
    let rest: Vec<&str> = args[1..].iter().map(|s| s.as_str()).collect();
    let id = gen_id();

    if flags.cli_annotate && cmd != "screenshot" {
        eprintln!(
            "{} --annotate only applies to the screenshot command",
            color::warning_indicator()
        );
    }

    match cmd {
        // === Navigation ===
        // Maps to "navigate" action in protocol; reflected in ACTION_CATEGORIES in action-policy.ts
        "open" | "goto" | "navigate" => {
            // `open` without a URL launches the browser but stays on
            // about:blank. Lets agents set up routes, cookies, or init
            // scripts before the first real navigation (see `batch`).
            // `goto` and `navigate` still require a URL since those verbs
            // imply the navigation itself.
            let first_url = rest.iter().find(|a| !a.starts_with("--"));
            let url = match first_url {
                Some(u) => *u,
                None if cmd == "open" => {
                    return Ok(json!({ "id": id, "action": "launch", "headless": !flags.headed }));
                }
                None => {
                    return Err(ParseError::MissingArguments {
                        context: cmd.to_string(),
                        usage: "goto <url>",
                    });
                }
            };
            let url_lower = url.to_lowercase();
            let url = if url_lower.starts_with("http://")
                || url_lower.starts_with("https://")
                || url_lower.starts_with("about:")
                || url_lower.starts_with("data:")
                || url_lower.starts_with("file:")
                || url_lower.starts_with("chrome-extension://")
                || url_lower.starts_with("chrome://")
            {
                url.to_string()
            } else {
                format!("https://{}", url)
            };
            let mut nav_cmd = json!({ "id": id, "action": "navigate", "url": url });
            if flags.provider.is_some() {
                nav_cmd["waitUntil"] = json!("none");
            }
            if let Some(ref headers_json) = flags.headers {
                let headers =
                    serde_json::from_str::<serde_json::Value>(headers_json).map_err(|_| {
                        ParseError::InvalidValue {
                            message: format!("Invalid JSON for --headers: {}", headers_json),
                            usage: "open <url> --headers '{\"Key\": \"Value\"}'",
                        }
                    })?;
                nav_cmd["headers"] = headers;
            }
            // Include iOS device info if specified (needed for auto-launch with existing daemon)
            if flags.provider.as_deref() == Some("ios") {
                if let Some(ref device) = flags.device {
                    nav_cmd["iosDevice"] = json!(device);
                }
            }
            Ok(nav_cmd)
        }
        "back" => Ok(json!({ "id": id, "action": "back" })),
        "forward" => Ok(json!({ "id": id, "action": "forward" })),
        "reload" => Ok(json!({ "id": id, "action": "reload" })),

        // === Core Actions ===
        "click" => {
            let new_tab = rest.contains(&"--new-tab");
            let sel = rest
                .iter()
                .find(|arg| **arg != "--new-tab")
                .ok_or_else(|| ParseError::MissingArguments {
                    context: "click".to_string(),
                    usage: "click <selector> [--new-tab]",
                })?;
            if new_tab {
                Ok(json!({ "id": id, "action": "click", "selector": sel, "newTab": true }))
            } else {
                Ok(json!({ "id": id, "action": "click", "selector": sel }))
            }
        }
        "dblclick" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "dblclick".to_string(),
                usage: "dblclick <selector>",
            })?;
            Ok(json!({ "id": id, "action": "dblclick", "selector": sel }))
        }
        "fill" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "fill".to_string(),
                usage: "fill <selector> <text>",
            })?;
            Ok(json!({ "id": id, "action": "fill", "selector": sel, "value": rest[1..].join(" ") }))
        }
        "type" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "type".to_string(),
                usage: "type <selector> <text>",
            })?;
            Ok(json!({ "id": id, "action": "type", "selector": sel, "text": rest[1..].join(" ") }))
        }
        "hover" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "hover".to_string(),
                usage: "hover <selector>",
            })?;
            Ok(json!({ "id": id, "action": "hover", "selector": sel }))
        }
        "focus" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "focus".to_string(),
                usage: "focus <selector>",
            })?;
            Ok(json!({ "id": id, "action": "focus", "selector": sel }))
        }
        "check" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "check".to_string(),
                usage: "check <selector>",
            })?;
            Ok(json!({ "id": id, "action": "check", "selector": sel }))
        }
        "uncheck" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "uncheck".to_string(),
                usage: "uncheck <selector>",
            })?;
            Ok(json!({ "id": id, "action": "uncheck", "selector": sel }))
        }
        "select" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "select".to_string(),
                usage: "select <selector> <value...>",
            })?;
            let _val = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "select".to_string(),
                usage: "select <selector> <value...>",
            })?;
            let values = &rest[1..];
            if values.len() == 1 {
                Ok(json!({ "id": id, "action": "select", "selector": sel, "values": values[0] }))
            } else {
                Ok(json!({ "id": id, "action": "select", "selector": sel, "values": values }))
            }
        }
        "drag" => {
            let src = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "drag".to_string(),
                usage: "drag <source> <target>",
            })?;
            let tgt = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "drag".to_string(),
                usage: "drag <source> <target>",
            })?;
            Ok(json!({ "id": id, "action": "drag", "source": src, "target": tgt }))
        }
        "upload" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "upload".to_string(),
                usage: "upload <selector> <files...>",
            })?;
            Ok(json!({ "id": id, "action": "upload", "selector": sel, "files": &rest[1..] }))
        }
        "download" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "download".to_string(),
                usage: "download <selector> <path>",
            })?;
            let path = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "download".to_string(),
                usage: "download <selector> <path>",
            })?;
            Ok(json!({ "id": id, "action": "download", "selector": sel, "path": path }))
        }

        // === Keyboard ===
        "press" | "key" => {
            let key = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "press".to_string(),
                usage: "press <key>",
            })?;
            Ok(json!({ "id": id, "action": "press", "key": key }))
        }
        "keydown" => {
            let key = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "keydown".to_string(),
                usage: "keydown <key>",
            })?;
            Ok(json!({ "id": id, "action": "keydown", "key": key }))
        }
        "keyup" => {
            let key = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "keyup".to_string(),
                usage: "keyup <key>",
            })?;
            Ok(json!({ "id": id, "action": "keyup", "key": key }))
        }
        "keyboard" => {
            let sub = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "keyboard".to_string(),
                usage: "keyboard <type|inserttext> <text>",
            })?;
            match *sub {
                "type" => {
                    let text: String = rest[1..].join(" ");
                    if text.is_empty() {
                        return Err(ParseError::MissingArguments {
                            context: "keyboard type".to_string(),
                            usage: "keyboard type <text>",
                        });
                    }
                    Ok(json!({ "id": id, "action": "keyboard", "subaction": "type", "text": text }))
                }
                "inserttext" | "insertText" => {
                    let text: String = rest[1..].join(" ");
                    if text.is_empty() {
                        return Err(ParseError::MissingArguments {
                            context: "keyboard inserttext".to_string(),
                            usage: "keyboard inserttext <text>",
                        });
                    }
                    Ok(
                        json!({ "id": id, "action": "keyboard", "subaction": "insertText", "text": text }),
                    )
                }
                _ => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: &["type", "inserttext"],
                }),
            }
        }

        // === Scroll ===
        "scroll" => {
            let mut cmd = json!({ "id": id, "action": "scroll" });
            let obj = cmd.as_object_mut().unwrap();
            let mut positional_index = 0;
            let mut i = 0;
            while i < rest.len() {
                match rest[i] {
                    "-s" | "--selector" => {
                        if let Some(s) = rest.get(i + 1) {
                            obj.insert("selector".to_string(), json!(s));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "scroll --selector".to_string(),
                                usage: "scroll [direction] [amount] [--selector <sel>]",
                            });
                        }
                    }
                    arg if arg.starts_with('-') => {}
                    _ => {
                        match positional_index {
                            0 => {
                                obj.insert("direction".to_string(), json!(rest[i]));
                            }
                            1 => {
                                if let Ok(n) = rest[i].parse::<i32>() {
                                    obj.insert("amount".to_string(), json!(n));
                                }
                            }
                            _ => {}
                        }
                        positional_index += 1;
                    }
                }
                i += 1;
            }
            if !obj.contains_key("direction") {
                obj.insert("direction".to_string(), json!("down"));
            }
            if !obj.contains_key("amount") {
                obj.insert("amount".to_string(), json!(300));
            }
            Ok(cmd)
        }
        "scrollintoview" | "scrollinto" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "scrollintoview".to_string(),
                usage: "scrollintoview <selector>",
            })?;
            Ok(json!({ "id": id, "action": "scrollintoview", "selector": sel }))
        }

        // === Wait ===
        "wait" => {
            // Check for --url flag: wait --url "**/dashboard" [--timeout ms]
            if let Some(idx) = rest.iter().position(|&s| s == "--url" || s == "-u") {
                let url = rest
                    .get(idx + 1)
                    .ok_or_else(|| ParseError::MissingArguments {
                        context: "wait --url".to_string(),
                        usage: "wait --url <pattern>",
                    })?;
                if url.is_empty() {
                    return Err(ParseError::InvalidValue {
                        message: "wait --url needs a non-empty pattern (an empty pattern would \
                                  match any URL)."
                            .to_string(),
                        usage: "wait --url <pattern>",
                    });
                }
                let mut cmd = json!({ "id": id, "action": "waitforurl", "url": url });
                // Parse --timeout (without it the default applies — and a
                // non-matching pattern would otherwise wait the full default).
                if let Some(t_idx) = rest.iter().position(|&s| s == "--timeout") {
                    if let Some(ms) = rest.get(t_idx + 1).and_then(|s| s.parse::<u64>().ok()) {
                        cmd["timeout"] = json!(ms);
                    }
                }
                return Ok(cmd);
            }

            // Check for --load flag: wait --load networkidle
            if let Some(idx) = rest.iter().position(|&s| s == "--load" || s == "-l") {
                let state = rest
                    .get(idx + 1)
                    .ok_or_else(|| ParseError::MissingArguments {
                        context: "wait --load".to_string(),
                        usage: "wait --load <state>",
                    })?;
                return Ok(json!({ "id": id, "action": "waitforloadstate", "state": state }));
            }

            // Check for --fn flag: wait --fn "window.ready === true"
            if let Some(idx) = rest.iter().position(|&s| s == "--fn" || s == "-f") {
                let expr = rest
                    .get(idx + 1)
                    .ok_or_else(|| ParseError::MissingArguments {
                        context: "wait --fn".to_string(),
                        usage: "wait --fn <expression>",
                    })?;
                return Ok(json!({ "id": id, "action": "waitforfunction", "expression": expr }));
            }

            // Check for --text flag: wait --text "Welcome" [--timeout ms]
            if let Some(idx) = rest.iter().position(|&s| s == "--text" || s == "-t") {
                let text = rest
                    .get(idx + 1)
                    .ok_or_else(|| ParseError::MissingArguments {
                        context: "wait --text".to_string(),
                        usage: "wait --text <text>",
                    })?;
                let mut cmd = json!({ "id": id, "action": "wait", "text": text });
                if let Some(t_idx) = rest.iter().position(|&s| s == "--timeout") {
                    if let Some(Ok(ms)) = rest.get(t_idx + 1).map(|s| s.parse::<u64>()) {
                        cmd["timeout"] = json!(ms);
                    }
                }
                return Ok(cmd);
            }

            // Check for --download flag: wait --download [path] [--timeout ms]
            if rest.iter().any(|&s| s == "--download" || s == "-d") {
                let mut cmd = json!({ "id": id, "action": "waitfordownload" });
                // Check for optional path (first non-flag argument after --download)
                let download_idx = rest
                    .iter()
                    .position(|&s| s == "--download" || s == "-d")
                    .unwrap();
                if let Some(path) = rest.get(download_idx + 1) {
                    if !path.starts_with("--") {
                        cmd["path"] = json!(path);
                    }
                }
                // Check for optional timeout
                if let Some(idx) = rest.iter().position(|&s| s == "--timeout") {
                    if let Some(timeout_str) = rest.get(idx + 1) {
                        if let Ok(timeout) = timeout_str.parse::<u64>() {
                            cmd["timeout"] = json!(timeout);
                        }
                    }
                }
                return Ok(cmd);
            }

            // --gone / --hidden: wait for an element to leave the DOM or
            // become invisible. Useful after a click that's supposed to
            // close a dialog, so the next command fails fast instead of
            // racing into a half-rendered UI.
            let state_override = if rest.iter().any(|&s| s == "--gone" || s == "--detached") {
                Some("detached")
            } else if rest.contains(&"--hidden") {
                Some("hidden")
            } else {
                None
            };

            // Default: selector or timeout
            // First non-flag positional is selector or numeric timeout
            let positional = rest.iter().find(|&&s| !s.starts_with("--"));
            let timeout_ms = rest
                .iter()
                .position(|&s| s == "--timeout")
                .and_then(|idx| rest.get(idx + 1))
                .and_then(|s| s.parse::<u64>().ok());

            if let Some(arg) = positional {
                if let Ok(timeout) = arg.parse::<u64>() {
                    Ok(json!({ "id": id, "action": "wait", "timeout": timeout }))
                } else {
                    let mut cmd = json!({ "id": id, "action": "wait", "selector": arg });
                    if let Some(state) = state_override {
                        cmd["state"] = json!(state);
                    }
                    if let Some(t) = timeout_ms {
                        cmd["timeout"] = json!(t);
                    }
                    Ok(cmd)
                }
            } else {
                Err(ParseError::MissingArguments {
                    context: "wait".to_string(),
                    usage: "wait <selector|ms> [--gone|--hidden] [--timeout ms]",
                })
            }
        }

        // === Screenshot/PDF ===
        "screenshot" => {
            // screenshot [selector] [path] [--full/-f]
            // selector: @ref or CSS selector
            // path: file path (contains / or . or ends with known extension)
            let mut full_page = false;
            let positional: Vec<&str> = rest
                .iter()
                .filter(|arg| match **arg {
                    "--full" | "-f" => {
                        full_page = true;
                        false
                    }
                    _ => true,
                })
                .copied()
                .collect();
            let (selector, path) = match (positional.first(), positional.get(1)) {
                (Some(first), Some(second)) => {
                    // Two args: first is selector, second is path
                    (Some(*first), Some(*second))
                }
                (Some(first), None) => {
                    // One arg: determine if it's a selector or a path
                    let is_relative_path = first.starts_with("./") || first.starts_with("../");
                    let is_selector = !is_relative_path
                        && (first.starts_with('.')
                            || first.starts_with('#')
                            || first.starts_with('@'));
                    let has_path_extension = first.ends_with(".png")
                        || first.ends_with(".jpg")
                        || first.ends_with(".jpeg")
                        || first.ends_with(".webp");
                    let is_path = is_relative_path || first.contains('/') || has_path_extension;
                    if is_selector || !is_path {
                        (Some(*first), None)
                    } else {
                        (None, Some(*first))
                    }
                }
                _ => (None, None),
            };
            let mut cmd = json!({
                "id": id, "action": "screenshot",
                "path": path, "selector": selector,
                "fullPage": full_page, "annotate": flags.annotate
            });
            if let Some(ref fmt) = flags.screenshot_format {
                cmd["format"] = json!(fmt);
            }
            if let Some(q) = flags.screenshot_quality {
                cmd["quality"] = json!(q);
                if flags.screenshot_format.as_deref() != Some("jpeg") {
                    eprintln!(
                        "{} --screenshot-quality is ignored for PNG; use --screenshot-format jpeg",
                        color::warning_indicator()
                    );
                }
            }
            if let Some(ref dir) = flags.screenshot_dir {
                cmd["screenshotDir"] = json!(dir);
            }
            Ok(cmd)
        }
        "pdf" => {
            let path = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "pdf".to_string(),
                usage: "pdf <path>",
            })?;
            Ok(json!({ "id": id, "action": "pdf", "path": path }))
        }

        // === Snapshot ===
        "snapshot" => {
            let mut cmd = json!({ "id": id, "action": "snapshot" });
            let obj = cmd.as_object_mut().unwrap();
            let mut i = 0;
            while i < rest.len() {
                match rest[i] {
                    "-i" | "--interactive" => {
                        obj.insert("interactive".to_string(), json!(true));
                    }
                    "-c" | "--compact" => {
                        obj.insert("compact".to_string(), json!(true));
                    }
                    "-C" | "--cursor" => {
                        obj.insert("cursor".to_string(), json!(true));
                    }
                    "-u" | "--urls" => {
                        obj.insert("urls".to_string(), json!(true));
                    }
                    "-d" | "--depth" => {
                        if let Some(d) = rest.get(i + 1) {
                            if let Ok(n) = d.parse::<i32>() {
                                obj.insert("maxDepth".to_string(), json!(n));
                                i += 1;
                            }
                        }
                    }
                    "-s" | "--selector" => {
                        if let Some(s) = rest.get(i + 1) {
                            obj.insert("selector".to_string(), json!(s));
                            i += 1;
                        }
                    }
                    _ => {}
                }
                i += 1;
            }
            Ok(cmd)
        }

        // === Eval ===
        "eval" => {
            // Check for flags: -b/--base64 or --stdin
            let (is_base64, is_stdin, script_parts): (bool, bool, &[&str]) =
                if rest.first() == Some(&"-b") || rest.first() == Some(&"--base64") {
                    (true, false, &rest[1..])
                } else if rest.first() == Some(&"--stdin") {
                    (false, true, &rest[1..])
                } else {
                    (false, false, rest.as_slice())
                };

            let script = if is_stdin {
                // Read script from stdin
                let stdin = io::stdin();
                let lines: Vec<String> = stdin
                    .lock()
                    .lines()
                    .map(|l| l.unwrap_or_default())
                    .collect();
                lines.join("\n")
            } else {
                let raw_script = script_parts.join(" ");
                if is_base64 {
                    let decoded =
                        STANDARD
                            .decode(&raw_script)
                            .map_err(|_| ParseError::InvalidValue {
                                message: "Invalid base64 encoding".to_string(),
                                usage: "eval -b <base64-encoded-script>",
                            })?;
                    String::from_utf8(decoded).map_err(|_| ParseError::InvalidValue {
                        message: "Base64 decoded to invalid UTF-8".to_string(),
                        usage: "eval -b <base64-encoded-script>",
                    })?
                } else {
                    raw_script
                }
            };
            Ok(json!({ "id": id, "action": "evaluate", "script": script }))
        }

        // === Close ===
        "close" | "quit" | "exit" => Ok(json!({ "id": id, "action": "close" })),

        // === Inspect ===
        "inspect" => Ok(json!({ "id": id, "action": "inspect" })),

        // === Authentication Vault ===
        "auth" => {
            let sub = rest.first().map(|s| s.as_ref());
            match sub {
                Some("save") => {
                    let name = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "auth save".to_string(),
                        usage: "agent-browser auth save <name> --url <url> --username <user> --password <pass>",
                    })?;

                    let mut url = None;
                    let mut username = None;
                    let mut password = None;
                    let mut password_stdin = false;
                    let mut username_selector = None;
                    let mut password_selector = None;
                    let mut submit_selector = None;

                    let mut j = 2;
                    while j < rest.len() {
                        match rest[j] {
                            "--url" => {
                                url = rest.get(j + 1).cloned();
                                j += 1;
                            }
                            "--username" => {
                                username = rest.get(j + 1).cloned();
                                j += 1;
                            }
                            "--password" => {
                                password = rest.get(j + 1).cloned();
                                j += 1;
                            }
                            "--password-stdin" => {
                                password_stdin = true;
                            }
                            "--username-selector" => {
                                username_selector = rest.get(j + 1).cloned();
                                j += 1;
                            }
                            "--password-selector" => {
                                password_selector = rest.get(j + 1).cloned();
                                j += 1;
                            }
                            "--submit-selector" => {
                                submit_selector = rest.get(j + 1).cloned();
                                j += 1;
                            }
                            other => {
                                if other.starts_with("--") {
                                    return Err(ParseError::InvalidValue {
                                        message: format!("unknown flag '{}' for auth save", other),
                                        usage: "agent-browser auth save <name> --url <url> --username <user> --password <pass>",
                                    });
                                }
                            }
                        }
                        j += 1;
                    }

                    let url_val = url.ok_or_else(|| ParseError::MissingArguments {
                        context: "auth save".to_string(),
                        usage: "agent-browser auth save <name> --url <url> --username <user> --password <pass> [--password-stdin]",
                    })?;
                    let user_val = username.ok_or_else(|| ParseError::MissingArguments {
                        context: "auth save".to_string(),
                        usage: "agent-browser auth save <name> --url <url> --username <user> --password <pass> [--password-stdin]",
                    })?;

                    if !password_stdin && password.is_none() {
                        return Err(ParseError::MissingArguments {
                            context: "auth save".to_string(),
                            usage: "agent-browser auth save <name> --url <url> --username <user> --password <pass> [--password-stdin]",
                        });
                    }

                    let mut cmd = json!({
                        "id": id,
                        "action": "auth_save",
                        "name": name,
                        "url": url_val,
                        "username": user_val,
                    });
                    if password_stdin {
                        cmd["passwordStdin"] = json!(true);
                    }
                    if let Some(pass_val) = password {
                        cmd["password"] = json!(pass_val);
                    }
                    if let Some(us) = username_selector {
                        cmd["usernameSelector"] = json!(us);
                    }
                    if let Some(ps) = password_selector {
                        cmd["passwordSelector"] = json!(ps);
                    }
                    if let Some(ss) = submit_selector {
                        cmd["submitSelector"] = json!(ss);
                    }
                    Ok(cmd)
                }
                Some("login") => {
                    let name = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "auth login".to_string(),
                        usage: "agent-browser auth login <name>",
                    })?;
                    Ok(json!({ "id": id, "action": "auth_login", "name": name }))
                }
                Some("list") => Ok(json!({ "id": id, "action": "auth_list" })),
                Some("delete") | Some("remove") => {
                    let name = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "auth delete".to_string(),
                        usage: "agent-browser auth delete <name>",
                    })?;
                    Ok(json!({ "id": id, "action": "auth_delete", "name": name }))
                }
                Some("show") => {
                    let name = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "auth show".to_string(),
                        usage: "agent-browser auth show <name>",
                    })?;
                    Ok(json!({ "id": id, "action": "auth_show", "name": name }))
                }
                _ => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.unwrap_or("(none)").to_string(),
                    valid_options: &["save", "login", "list", "delete", "show"],
                }),
            }
        }

        // === Action Confirmation ===
        "confirm" => {
            let cid = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "confirm".to_string(),
                usage: "agent-browser confirm <confirmation-id>",
            })?;
            Ok(json!({ "id": id, "action": "confirm", "confirmationId": cid }))
        }
        "deny" => {
            let cid = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "deny".to_string(),
                usage: "agent-browser deny <confirmation-id>",
            })?;
            Ok(json!({ "id": id, "action": "deny", "confirmationId": cid }))
        }

        // === Connect (CDP) ===
        "connect" => {
            let endpoint = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "connect".to_string(),
                usage: "connect <port|url>",
            })?;
            // Check if it's a URL (ws://, wss://, http://, https://)
            if endpoint.starts_with("ws://")
                || endpoint.starts_with("wss://")
                || endpoint.starts_with("http://")
                || endpoint.starts_with("https://")
            {
                Ok(json!({ "id": id, "action": "launch", "cdpUrl": endpoint }))
            } else {
                // It's a port number - validate and use cdpPort field
                let port: u16 = match endpoint.parse::<u32>() {
                    Ok(0) => {
                        return Err(ParseError::InvalidValue {
                            message: "Invalid port: port must be greater than 0".to_string(),
                            usage: "connect <port|url>",
                        });
                    }
                    Ok(p) if p > 65535 => {
                        return Err(ParseError::InvalidValue {
                            message: format!(
                                "Invalid port: {} is out of range (valid range: 1-65535)",
                                p
                            ),
                            usage: "connect <port|url>",
                        });
                    }
                    Ok(p) => p as u16,
                    Err(_) => {
                        return Err(ParseError::InvalidValue {
                            message: format!(
                                "Invalid value: '{}' is not a valid port number or URL",
                                endpoint
                            ),
                            usage: "connect <port|url>",
                        });
                    }
                };
                Ok(json!({ "id": id, "action": "launch", "cdpPort": port }))
            }
        }

        // === Runtime stream control ===
        "stream" => match rest.first().copied() {
            Some("enable") => {
                let mut cmd = json!({ "id": id, "action": "stream_enable" });
                let mut i = 1;
                while i < rest.len() {
                    match rest[i] {
                        "--port" => {
                            let value =
                                rest.get(i + 1)
                                    .ok_or_else(|| ParseError::MissingArguments {
                                        context: "stream enable --port".to_string(),
                                        usage: "stream enable [--port <port>]",
                                    })?;
                            let port =
                                value.parse::<u32>().map_err(|_| ParseError::InvalidValue {
                                    message: format!(
                                        "Invalid port: '{}' is not a valid integer",
                                        value
                                    ),
                                    usage: "stream enable [--port <port>]",
                                })?;
                            if port > u16::MAX as u32 {
                                return Err(ParseError::InvalidValue {
                                    message: format!(
                                        "Invalid port: {} is out of range (valid range: 0-65535)",
                                        port
                                    ),
                                    usage: "stream enable [--port <port>]",
                                });
                            }
                            cmd["port"] = json!(port);
                            i += 2;
                        }
                        flag => {
                            return Err(ParseError::InvalidValue {
                                message: format!("Unknown flag for stream enable: {}", flag),
                                usage: "stream enable [--port <port>]",
                            });
                        }
                    }
                }
                Ok(cmd)
            }
            Some("disable") => Ok(json!({ "id": id, "action": "stream_disable" })),
            Some("status") => Ok(json!({ "id": id, "action": "stream_status" })),
            Some(sub) => Err(ParseError::UnknownSubcommand {
                subcommand: sub.to_string(),
                valid_options: &["enable", "disable", "status"],
            }),
            None => Err(ParseError::MissingArguments {
                context: "stream".to_string(),
                usage: "stream <enable|disable|status>",
            }),
        },

        // === Get ===
        "get" => parse_get(&rest, &id),

        // Top-level shortcuts for `get <x>` status reads — users naturally type
        // `agent-browser url` / `cdp-url` / `title` without the `get` prefix
        // (and expect `cdp-url`/`cdp_url` to work interchangeably).
        "url" | "cdp-url" | "cdp_url" | "title" | "html" | "text" | "value" | "count" | "box"
        | "styles" | "attr" => {
            let sub = if cmd == "cdp_url" { "cdp-url" } else { cmd };
            let mut get_args: Vec<&str> = Vec::with_capacity(rest.len() + 1);
            get_args.push(sub);
            get_args.extend_from_slice(&rest);
            parse_get(&get_args, &id)
        }

        // === Is (state checks) ===
        "is" => parse_is(&rest, &id),

        // === Find (locators) ===
        "find" => parse_find(&rest, &id),

        // === Mouse ===
        "mouse" => parse_mouse(&rest, &id),

        // === Set (browser settings) ===
        "set" => parse_set(&rest, &id),

        // === Network ===
        "network" => parse_network(&rest, &id),

        // === Storage ===
        "storage" => parse_storage(&rest, &id),

        // === Cookies ===
        "cookies" => {
            let op = rest.first().unwrap_or(&"get");
            match *op {
                "set" => {
                    // --curl <file> mode: import cookies from a JSON array,
                    // raw cURL dump, or bare Cookie header. Scoped to the
                    // host of --domain if provided; otherwise the cookies
                    // have no scope (daemon falls back to current origin).
                    if let Some(curl_idx) = rest.iter().position(|a| *a == "--curl") {
                        let path =
                            rest.get(curl_idx + 1)
                                .ok_or_else(|| {
                                    ParseError::MissingArguments {
                            context: "cookies set --curl".to_string(),
                            usage: "cookies set --curl <file> [--domain <domain>] [--url <url>]",
                        }
                                })?;
                        let raw = std::fs::read_to_string(path).map_err(|e| {
                            ParseError::InvalidValue {
                                message: format!("cookies --curl: cannot read '{}': {}", path, e),
                                usage: "cookies set --curl <file>",
                            }
                        })?;
                        let mut cookies =
                            parse_curl_cookies(&raw).map_err(|e| ParseError::InvalidValue {
                                message: format!("cookies --curl: {}", e),
                                usage: "cookies set --curl <file>",
                            })?;

                        let domain_idx = rest.iter().position(|a| *a == "--domain");
                        let domain = domain_idx.and_then(|i| rest.get(i + 1).copied());
                        let url_idx = rest.iter().position(|a| *a == "--url");
                        let url = url_idx.and_then(|i| rest.get(i + 1).copied());

                        for cookie in cookies.iter_mut() {
                            if let Some(d) = domain {
                                cookie["domain"] = json!(d);
                                cookie["path"] = cookie.get("path").cloned().unwrap_or(json!("/"));
                            }
                            if let Some(u) = url {
                                cookie["url"] = json!(u);
                            }
                        }

                        return Ok(json!({
                            "id": id,
                            "action": "cookies_set",
                            "cookies": cookies,
                        }));
                    }

                    let name = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "cookies set".to_string(),
                        usage: "cookies set <name> <value> [--url <url>] [--domain <domain>] [--path <path>] [--httpOnly] [--secure] [--sameSite <Strict|Lax|None>] [--expires <timestamp>]\n  or:  cookies set --curl <file> [--domain <domain>] [--url <url>]",
                    })?;
                    let value = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                        context: "cookies set".to_string(),
                        usage: "cookies set <name> <value> [--url <url>] [--domain <domain>] [--path <path>] [--httpOnly] [--secure] [--sameSite <Strict|Lax|None>] [--expires <timestamp>]\n  or:  cookies set --curl <file> [--domain <domain>] [--url <url>]",
                    })?;

                    let mut cookie = json!({ "name": name, "value": value });

                    // Parse optional flags
                    let mut i = 3;
                    while i < rest.len() {
                        match rest[i] {
                            "--url" => {
                                if let Some(url) = rest.get(i + 1) {
                                    cookie["url"] = json!(url);
                                    i += 2;
                                } else {
                                    return Err(ParseError::MissingArguments {
                                        context: "cookies set --url".to_string(),
                                        usage: "--url <url>",
                                    });
                                }
                            }
                            "--domain" => {
                                if let Some(domain) = rest.get(i + 1) {
                                    cookie["domain"] = json!(domain);
                                    i += 2;
                                } else {
                                    return Err(ParseError::MissingArguments {
                                        context: "cookies set --domain".to_string(),
                                        usage: "--domain <domain>",
                                    });
                                }
                            }
                            "--path" => {
                                if let Some(path) = rest.get(i + 1) {
                                    cookie["path"] = json!(path);
                                    i += 2;
                                } else {
                                    return Err(ParseError::MissingArguments {
                                        context: "cookies set --path".to_string(),
                                        usage: "--path <path>",
                                    });
                                }
                            }
                            "--httpOnly" => {
                                cookie["httpOnly"] = json!(true);
                                i += 1;
                            }
                            "--secure" => {
                                cookie["secure"] = json!(true);
                                i += 1;
                            }
                            "--sameSite" => {
                                if let Some(same_site) = rest.get(i + 1) {
                                    // Validate sameSite value
                                    if *same_site == "Strict"
                                        || *same_site == "Lax"
                                        || *same_site == "None"
                                    {
                                        cookie["sameSite"] = json!(same_site);
                                        i += 2;
                                    } else {
                                        return Err(ParseError::MissingArguments {
                                            context: "cookies set --sameSite".to_string(),
                                            usage: "--sameSite <Strict|Lax|None>",
                                        });
                                    }
                                } else {
                                    return Err(ParseError::MissingArguments {
                                        context: "cookies set --sameSite".to_string(),
                                        usage: "--sameSite <Strict|Lax|None>",
                                    });
                                }
                            }
                            "--expires" => {
                                if let Some(expires_str) = rest.get(i + 1) {
                                    if let Ok(expires) = expires_str.parse::<i64>() {
                                        cookie["expires"] = json!(expires);
                                        i += 2;
                                    } else {
                                        return Err(ParseError::MissingArguments {
                                            context: "cookies set --expires".to_string(),
                                            usage: "--expires <timestamp>",
                                        });
                                    }
                                } else {
                                    return Err(ParseError::MissingArguments {
                                        context: "cookies set --expires".to_string(),
                                        usage: "--expires <timestamp>",
                                    });
                                }
                            }
                            _ => {
                                // Unknown flag, skip it (or could error)
                                i += 1;
                            }
                        }
                    }

                    Ok(json!({ "id": id, "action": "cookies_set", "cookies": [cookie] }))
                }
                "clear" => Ok(json!({ "id": id, "action": "cookies_clear" })),
                _ => Ok(json!({ "id": id, "action": "cookies_get" })),
            }
        }

        // === Tabs ===
        "tab" => {
            match rest.first().copied() {
                Some("new") => {
                    // Accepted forms:
                    //   tab new [url]
                    //   tab new --label <name> [url]
                    //   tab new [url] --label <name>
                    let mut cmd = json!({ "id": id, "action": "tab_new" });
                    let mut i = 1;
                    while i < rest.len() {
                        match rest[i] {
                            "--label" => {
                                let name = rest.get(i + 1).ok_or(ParseError::MissingArguments {
                                    context: "tab new --label".to_string(),
                                    usage: "tab new --label <name> [url]",
                                })?;
                                cmd["label"] = json!(name);
                                i += 2;
                            }
                            other if !other.starts_with("--") && cmd.get("url").is_none() => {
                                cmd["url"] = json!(other);
                                i += 1;
                            }
                            other => {
                                return Err(ParseError::UnknownSubcommand {
                                    subcommand: other.to_string(),
                                    valid_options: &["--label", "<url>"],
                                });
                            }
                        }
                    }
                    Ok(cmd)
                }
                Some("list") => Ok(json!({ "id": id, "action": "tab_list" })),
                Some("close") => {
                    let mut cmd = json!({ "id": id, "action": "tab_close" });
                    if let Some(tab_ref) = rest.get(1) {
                        cmd["tabId"] = json!(tab_ref);
                    }
                    Ok(cmd)
                }
                Some(tab_ref) => Ok(json!({
                    "id": id,
                    "action": "tab_switch",
                    "tabId": tab_ref,
                })),
                None => Ok(json!({ "id": id, "action": "tab_list" })),
            }
        }

        // === Window ===
        "window" => {
            const VALID: &[&str] = &["new"];
            match rest.first().copied() {
                Some("new") => Ok(json!({ "id": id, "action": "window_new" })),
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "window".to_string(),
                    usage: "window <new>",
                }),
            }
        }

        // === Frame ===
        "frame" => {
            if rest.first().copied() == Some("main") {
                Ok(json!({ "id": id, "action": "mainframe" }))
            } else {
                let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                    context: "frame".to_string(),
                    usage: "frame <selector|main>",
                })?;
                Ok(json!({ "id": id, "action": "frame", "selector": sel }))
            }
        }

        // === Dialog ===
        "dialog" => {
            const VALID: &[&str] = &["accept", "dismiss", "status"];
            match rest.first().copied() {
                Some("accept") => {
                    let mut cmd = json!({ "id": id, "action": "dialog", "response": "accept" });
                    if let Some(prompt_text) = rest.get(1) {
                        cmd["promptText"] = json!(prompt_text);
                    }
                    Ok(cmd)
                }
                Some("dismiss") => {
                    let mut cmd = json!({ "id": id, "action": "dialog", "response": "dismiss" });
                    if let Some(prompt_text) = rest.get(1) {
                        cmd["promptText"] = json!(prompt_text);
                    }
                    Ok(cmd)
                }
                Some("status") => Ok(json!({ "id": id, "action": "dialog", "response": "status" })),
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "dialog".to_string(),
                    usage: "dialog <accept|dismiss|status> [text]",
                }),
            }
        }

        // === Debug ===
        "trace" => {
            const VALID: &[&str] = &["start", "stop"];
            match rest.first().copied() {
                Some("start") => Ok(json!({ "id": id, "action": "trace_start" })),
                Some("stop") => {
                    let mut cmd = json!({ "id": id, "action": "trace_stop" });
                    if let Some(path) = rest.get(1) {
                        cmd["path"] = json!(path);
                    }
                    Ok(cmd)
                }
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "trace".to_string(),
                    usage: "trace <start|stop> [path]",
                }),
            }
        }

        // === Profiler (CDP Tracing / Chromium profiling) ===
        "profiler" => {
            const VALID: &[&str] = &["start", "stop"];
            match rest.first().copied() {
                Some("start") => {
                    let mut cmd = json!({ "id": id, "action": "profiler_start" });
                    if let Some(idx) = rest.iter().position(|s| *s == "--categories") {
                        if let Some(cats) = rest.get(idx + 1) {
                            let categories: Vec<&str> = cats.split(',').collect();
                            cmd["categories"] = json!(categories);
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "profiler start --categories".to_string(),
                                usage: "--categories <list>",
                            });
                        }
                    }
                    Ok(cmd)
                }
                Some("stop") => {
                    let mut cmd = json!({ "id": id, "action": "profiler_stop" });
                    if let Some(path) = rest.get(1) {
                        cmd["path"] = json!(path);
                    }
                    Ok(cmd)
                }
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "profiler".to_string(),
                    usage: "profiler <start|stop> [options]",
                }),
            }
        }

        // === Recording (browser video recording) ===
        "record" => {
            const VALID: &[&str] = &["start", "stop", "restart"];
            match rest.first().copied() {
                Some("start") => {
                    let path = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "record start".to_string(),
                        usage: "record start <output.webm> [url]",
                    })?;
                    // Optional URL parameter
                    let url = rest.get(2);
                    let mut cmd = json!({ "id": id, "action": "recording_start", "path": path });
                    if let Some(u) = url {
                        // Add https:// prefix if needed (preserve special schemes)
                        let url_str = if u.starts_with("http") || u.contains("://") {
                            u.to_string()
                        } else {
                            format!("https://{}", u)
                        };
                        cmd["url"] = json!(url_str);
                    }
                    Ok(cmd)
                }
                Some("stop") => Ok(json!({ "id": id, "action": "recording_stop" })),
                Some("restart") => {
                    let path = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "record restart".to_string(),
                        usage: "record restart <output.webm> [url]",
                    })?;
                    // Optional URL parameter
                    let url = rest.get(2);
                    let mut cmd = json!({ "id": id, "action": "recording_restart", "path": path });
                    if let Some(u) = url {
                        // Add https:// prefix if needed (preserve special schemes)
                        let url_str = if u.starts_with("http") || u.contains("://") {
                            u.to_string()
                        } else {
                            format!("https://{}", u)
                        };
                        cmd["url"] = json!(url_str);
                    }
                    Ok(cmd)
                }
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "record".to_string(),
                    usage: "record <start|stop|restart> [path] [url]",
                }),
            }
        }
        "console" => {
            let clear = rest.contains(&"--clear");
            Ok(json!({ "id": id, "action": "console", "clear": clear }))
        }
        "errors" => {
            let clear = rest.contains(&"--clear");
            Ok(json!({ "id": id, "action": "errors", "clear": clear }))
        }
        "highlight" => {
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "highlight".to_string(),
                usage: "highlight <selector>",
            })?;
            Ok(json!({ "id": id, "action": "highlight", "selector": sel }))
        }

        // === Clipboard ===
        "clipboard" => match rest.first().copied() {
            Some("read") | None => {
                Ok(json!({ "id": id, "action": "clipboard", "operation": "read" }))
            }
            Some("write") => {
                rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                    context: "clipboard write".to_string(),
                    usage: "clipboard write <text>",
                })?;
                let text = rest[1..].join(" ");
                Ok(json!({ "id": id, "action": "clipboard", "operation": "write", "text": text }))
            }
            Some("copy") => Ok(json!({ "id": id, "action": "clipboard", "operation": "copy" })),
            Some("paste") => Ok(json!({ "id": id, "action": "clipboard", "operation": "paste" })),
            Some(sub) => Err(ParseError::UnknownSubcommand {
                subcommand: sub.to_string(),
                valid_options: &["read", "write", "copy", "paste"],
            }),
        },

        // === State ===
        "state" => {
            const VALID: &[&str] = &["save", "load", "list", "clear", "show", "clean", "rename"];
            match rest.first().copied() {
                Some("save") => {
                    let path = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "state save".to_string(),
                        usage: "state save <path>",
                    })?;
                    Ok(json!({ "id": id, "action": "state_save", "path": path }))
                }
                Some("load") => {
                    let path = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "state load".to_string(),
                        usage: "state load <path>",
                    })?;
                    Ok(json!({ "id": id, "action": "state_load", "path": path }))
                }
                Some("list") => Ok(json!({ "id": id, "action": "state_list" })),
                Some("clear") => {
                    let mut session_name: Option<&str> = None;
                    let mut all = false;

                    let mut i = 1;
                    while i < rest.len() {
                        match rest[i] {
                            "--all" | "-a" => {
                                all = true;
                            }
                            arg if !arg.starts_with('-') => {
                                session_name = Some(arg);
                            }
                            _ => {}
                        }
                        i += 1;
                    }

                    if let Some(name) = session_name {
                        if !is_valid_session_name(name) {
                            return Err(ParseError::InvalidSessionName {
                                name: name.to_string(),
                            });
                        }
                    }

                    let mut cmd = json!({ "id": id, "action": "state_clear" });
                    if all {
                        cmd["all"] = json!(true);
                    }
                    if let Some(name) = session_name {
                        cmd["sessionName"] = json!(name);
                    }
                    Ok(cmd)
                }
                Some("show") => {
                    let filename = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "state show".to_string(),
                        usage: "state show <filename>",
                    })?;
                    Ok(json!({ "id": id, "action": "state_show", "path": filename }))
                }
                Some("clean") => {
                    let mut days: Option<i64> = None;

                    let mut i = 1;
                    while i < rest.len() {
                        if rest[i] == "--older-than" {
                            if let Some(d) = rest.get(i + 1) {
                                days = d.parse().ok();
                                i += 1;
                            }
                        }
                        i += 1;
                    }

                    let days = days.ok_or_else(|| ParseError::MissingArguments {
                        context: "state clean".to_string(),
                        usage: "state clean --older-than <days>",
                    })?;

                    Ok(json!({ "id": id, "action": "state_clean", "days": days }))
                }
                Some("rename") => {
                    let old_name = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                        context: "state rename".to_string(),
                        usage: "state rename <old-name> <new-name>",
                    })?;
                    let new_name = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                        context: "state rename".to_string(),
                        usage: "state rename <old-name> <new-name>",
                    })?;
                    let old_name = old_name.trim_end_matches(".json");
                    let new_name = new_name.trim_end_matches(".json");

                    if !is_valid_session_name(old_name) {
                        return Err(ParseError::InvalidSessionName {
                            name: old_name.to_string(),
                        });
                    }
                    if !is_valid_session_name(new_name) {
                        return Err(ParseError::InvalidSessionName {
                            name: new_name.to_string(),
                        });
                    }

                    Ok(
                        json!({ "id": id, "action": "state_rename", "oldName": old_name, "newName": new_name }),
                    )
                }
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "state".to_string(),
                    usage: "state <save|load|list|clear|show|clean|rename> ...",
                }),
            }
        }

        // === iOS-specific commands ===
        "tap" => {
            // Alias for click (semantic clarity for touch interfaces)
            let sel = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "tap".to_string(),
                usage: "tap <selector>",
            })?;
            Ok(json!({ "id": id, "action": "tap", "selector": sel }))
        }
        "swipe" => {
            let direction = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "swipe".to_string(),
                usage: "swipe <up|down|left|right> [distance]",
            })?;
            let valid_directions = ["up", "down", "left", "right"];
            if !valid_directions.contains(direction) {
                return Err(ParseError::InvalidValue {
                    message: format!("Invalid swipe direction: {}", direction),
                    usage: "swipe <up|down|left|right> [distance]",
                });
            }
            let mut cmd = json!({ "id": id, "action": "swipe", "direction": direction });
            if let Some(distance) = rest.get(1) {
                if let Ok(d) = distance.parse::<u32>() {
                    cmd.as_object_mut()
                        .unwrap()
                        .insert("distance".to_string(), json!(d));
                }
            }
            Ok(cmd)
        }
        "device" => {
            match rest.first().copied() {
                Some("list") | None => {
                    // List available iOS simulators
                    Ok(json!({ "id": id, "action": "device_list" }))
                }
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: &["list"],
                }),
            }
        }

        "diff" => parse_diff(&rest, &id),

        // === Batch ===
        "batch" => {
            let bail = rest.contains(&"--bail");
            let commands: Vec<&str> = rest.iter().filter(|a| **a != "--bail").copied().collect();
            let mut cmd = json!({ "id": id, "action": "batch", "bail": bail });
            if !commands.is_empty() {
                cmd["commands"] = json!(commands);
            }
            Ok(cmd)
        }

        // === React (requires `open --enable react-devtools`) ===
        "react" => parse_react(&rest, &id),

        // === Core Web Vitals + hydration ===
        "vitals" | "web-vitals" => {
            let mut cmd = json!({ "id": id, "action": "vitals" });
            let json_out = rest.contains(&"--json");
            if json_out {
                cmd["json"] = json!(true);
            }
            if let Some(url) = rest.iter().find(|a| !a.starts_with("--")) {
                cmd["url"] = json!(url);
            }
            Ok(cmd)
        }

        // === SPA client-side navigation ===
        "pushstate" => {
            let url = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "pushstate".to_string(),
                usage: "pushstate <url>",
            })?;
            Ok(json!({ "id": id, "action": "pushstate", "url": url }))
        }

        // === Remove init script ===
        "removeinitscript" => {
            let identifier = rest.first().ok_or_else(|| ParseError::MissingArguments {
                context: "removeinitscript".to_string(),
                usage: "removeinitscript <identifier>",
            })?;
            Ok(json!({ "id": id, "action": "removeinitscript", "identifier": identifier }))
        }

        _ => Err(ParseError::UnknownCommand {
            command: cmd.to_string(),
        }),
    }
}

fn parse_react(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &["tree", "inspect", "renders", "suspense"];
    let sub = rest.first().copied().ok_or(ParseError::MissingArguments {
        context: "react".to_string(),
        usage: "react <tree|inspect|renders|suspense>",
    })?;
    let json_out = rest.contains(&"--json");
    let flag = |key: &str| -> Value {
        if json_out {
            json!({ "id": id, "action": key, "json": true })
        } else {
            json!({ "id": id, "action": key })
        }
    };
    match sub {
        "tree" => Ok(flag("react_tree")),
        "inspect" => {
            let id_arg = rest
                .iter()
                .skip(1)
                .find(|a| !a.starts_with("--"))
                .copied()
                .ok_or(ParseError::MissingArguments {
                    context: "react inspect".to_string(),
                    usage: "react inspect <id>",
                })?;
            let numeric: i64 = id_arg.parse().map_err(|_| ParseError::InvalidValue {
                message: format!("react inspect id must be a number, got '{}'", id_arg),
                usage: "react inspect <id>",
            })?;
            let mut cmd = json!({ "id": id, "action": "react_inspect", "fiberId": numeric });
            if json_out {
                cmd["json"] = json!(true);
            }
            Ok(cmd)
        }
        "renders" => {
            let op = rest.get(1).copied().unwrap_or("start");
            match op {
                "start" => Ok(flag("react_renders_start")),
                "stop" => Ok(flag("react_renders_stop")),
                other => Err(ParseError::UnknownSubcommand {
                    subcommand: other.to_string(),
                    valid_options: &["start", "stop"],
                }),
            }
        }
        "suspense" => {
            let only_dynamic = rest.contains(&"--only-dynamic");
            let mut cmd = json!({ "id": id, "action": "react_suspense" });
            if json_out {
                cmd["json"] = json!(true);
            }
            if only_dynamic {
                cmd["onlyDynamic"] = json!(true);
            }
            Ok(cmd)
        }
        other => Err(ParseError::UnknownSubcommand {
            subcommand: other.to_string(),
            valid_options: VALID,
        }),
    }
}

fn parse_diff(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &["snapshot", "screenshot", "url"];

    match rest.first().copied() {
        Some("snapshot") => {
            let mut cmd = json!({ "id": id, "action": "diff_snapshot" });
            let obj = cmd.as_object_mut().unwrap();
            let mut i = 1;
            while i < rest.len() {
                match rest[i] {
                    "-b" | "--baseline" => {
                        if let Some(path) = rest.get(i + 1) {
                            obj.insert("baseline".to_string(), json!(path));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff snapshot --baseline".to_string(),
                                usage: "diff snapshot --baseline <file>",
                            });
                        }
                    }
                    "-s" | "--selector" => {
                        if let Some(s) = rest.get(i + 1) {
                            obj.insert("selector".to_string(), json!(s));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff snapshot --selector".to_string(),
                                usage: "diff snapshot --selector <sel>",
                            });
                        }
                    }
                    "-c" | "--compact" => {
                        obj.insert("compact".to_string(), json!(true));
                    }
                    "-d" | "--depth" => {
                        if let Some(d) = rest.get(i + 1) {
                            match d.parse::<u32>() {
                                Ok(n) => {
                                    obj.insert("maxDepth".to_string(), json!(n));
                                    i += 1;
                                }
                                Err(_) => {
                                    return Err(ParseError::InvalidValue {
                                        message: format!(
                                            "Depth must be a non-negative integer, got: {}",
                                            d
                                        ),
                                        usage: "diff snapshot --depth <n>",
                                    });
                                }
                            }
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff snapshot --depth".to_string(),
                                usage: "diff snapshot --depth <n>",
                            });
                        }
                    }
                    other if other.starts_with('-') => {
                        return Err(ParseError::InvalidValue {
                            message: format!("Unknown flag: {}", other),
                            usage: "diff snapshot [--baseline <file>] [--selector <sel>] [--compact] [--depth <n>]",
                        });
                    }
                    other => {
                        return Err(ParseError::InvalidValue {
                            message: format!("Unexpected argument: {}", other),
                            usage: "diff snapshot [--baseline <file>] [--selector <sel>] [--compact] [--depth <n>]",
                        });
                    }
                }
                i += 1;
            }
            Ok(cmd)
        }
        Some("screenshot") => {
            let mut cmd = json!({ "id": id, "action": "diff_screenshot" });
            let obj = cmd.as_object_mut().unwrap();
            let mut i = 1;
            while i < rest.len() {
                match rest[i] {
                    "-b" | "--baseline" => {
                        if let Some(path) = rest.get(i + 1) {
                            obj.insert("baseline".to_string(), json!(path));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff screenshot --baseline".to_string(),
                                usage: "diff screenshot --baseline <file>",
                            });
                        }
                    }
                    "-o" | "--output" => {
                        if let Some(path) = rest.get(i + 1) {
                            obj.insert("output".to_string(), json!(path));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff screenshot --output".to_string(),
                                usage: "diff screenshot --output <file>",
                            });
                        }
                    }
                    "-t" | "--threshold" => {
                        if let Some(t) = rest.get(i + 1) {
                            match t.parse::<f64>() {
                                Ok(n) if (0.0..=1.0).contains(&n) => {
                                    obj.insert("threshold".to_string(), json!(n));
                                    i += 1;
                                }
                                Ok(n) => {
                                    return Err(ParseError::InvalidValue {
                                        message: format!(
                                            "Threshold must be between 0 and 1, got {}",
                                            n
                                        ),
                                        usage: "diff screenshot --threshold <0-1>",
                                    });
                                }
                                Err(_) => {
                                    return Err(ParseError::InvalidValue {
                                        message: format!("Invalid threshold value: {}", t),
                                        usage: "diff screenshot --threshold <0-1>",
                                    });
                                }
                            }
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff screenshot --threshold".to_string(),
                                usage: "diff screenshot --threshold <0-1>",
                            });
                        }
                    }
                    "-s" | "--selector" => {
                        if let Some(s) = rest.get(i + 1) {
                            obj.insert("selector".to_string(), json!(s));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff screenshot --selector".to_string(),
                                usage: "diff screenshot --selector <sel>",
                            });
                        }
                    }
                    "--full" | "-f" => {
                        obj.insert("fullPage".to_string(), json!(true));
                    }
                    other if other.starts_with('-') => {
                        return Err(ParseError::InvalidValue {
                            message: format!("Unknown flag: {}", other),
                            usage: "diff screenshot --baseline <file> [--output <file>] [--threshold <0-1>] [--selector <sel>] [--full/-f]",
                        });
                    }
                    other => {
                        return Err(ParseError::InvalidValue {
                            message: format!("Unexpected argument: {}", other),
                            usage: "diff screenshot --baseline <file> [--output <file>] [--threshold <0-1>] [--selector <sel>] [--full/-f]",
                        });
                    }
                }
                i += 1;
            }
            if !obj.contains_key("baseline") {
                return Err(ParseError::MissingArguments {
                    context: "diff screenshot".to_string(),
                    usage: "diff screenshot --baseline <file>",
                });
            }
            Ok(cmd)
        }
        Some("url") => {
            let url1 = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "diff url".to_string(),
                usage: "diff url <url1> <url2>",
            })?;
            let url2 = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "diff url".to_string(),
                usage: "diff url <url1> <url2>",
            })?;
            let mut cmd = json!({
                "id": id,
                "action": "diff_url",
                "url1": url1,
                "url2": url2,
            });
            let obj = cmd.as_object_mut().unwrap();
            let mut i = 3;
            while i < rest.len() {
                match rest[i] {
                    "--screenshot" => {
                        obj.insert("screenshot".to_string(), json!(true));
                    }
                    "--full" | "-f" => {
                        obj.insert("fullPage".to_string(), json!(true));
                    }
                    "--wait-until" => {
                        if let Some(val) = rest.get(i + 1) {
                            obj.insert("waitUntil".to_string(), json!(val));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff url --wait-until".to_string(),
                                usage: "diff url <url1> <url2> --wait-until <load|domcontentloaded|networkidle>",
                            });
                        }
                    }
                    "-s" | "--selector" => {
                        if let Some(s) = rest.get(i + 1) {
                            obj.insert("selector".to_string(), json!(s));
                            i += 1;
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff url --selector".to_string(),
                                usage: "diff url <url1> <url2> --selector <sel>",
                            });
                        }
                    }
                    "-c" | "--compact" => {
                        obj.insert("compact".to_string(), json!(true));
                    }
                    "-d" | "--depth" => {
                        if let Some(d) = rest.get(i + 1) {
                            match d.parse::<u32>() {
                                Ok(n) => {
                                    obj.insert("maxDepth".to_string(), json!(n));
                                    i += 1;
                                }
                                Err(_) => {
                                    return Err(ParseError::InvalidValue {
                                        message: format!(
                                            "Depth must be a non-negative integer, got: {}",
                                            d
                                        ),
                                        usage: "diff url <url1> <url2> --depth <n>",
                                    });
                                }
                            }
                        } else {
                            return Err(ParseError::MissingArguments {
                                context: "diff url --depth".to_string(),
                                usage: "diff url <url1> <url2> --depth <n>",
                            });
                        }
                    }
                    other if other.starts_with('-') => {
                        return Err(ParseError::InvalidValue {
                            message: format!("Unknown flag: {}", other),
                            usage: "diff url <url1> <url2> [--screenshot] [--full/-f] [--wait-until <strategy>] [--selector <sel>] [--compact] [--depth <n>]",
                        });
                    }
                    other => {
                        return Err(ParseError::InvalidValue {
                            message: format!("Unexpected argument: {}", other),
                            usage: "diff url <url1> <url2> [--screenshot] [--full/-f] [--wait-until <strategy>] [--selector <sel>] [--compact] [--depth <n>]",
                        });
                    }
                }
                i += 1;
            }
            Ok(cmd)
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "diff".to_string(),
            usage: "diff <snapshot|screenshot|url>",
        }),
    }
}

fn parse_get(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &[
        "text", "html", "value", "attr", "url", "title", "count", "box", "styles", "cdp-url",
    ];

    match rest.first().copied() {
        Some("text") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get text".to_string(),
                usage: "get text <selector>",
            })?;
            Ok(json!({ "id": id, "action": "gettext", "selector": sel }))
        }
        Some("html") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get html".to_string(),
                usage: "get html <selector>",
            })?;
            Ok(json!({ "id": id, "action": "innerhtml", "selector": sel }))
        }
        Some("value") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get value".to_string(),
                usage: "get value <selector>",
            })?;
            Ok(json!({ "id": id, "action": "inputvalue", "selector": sel }))
        }
        Some("attr") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get attr".to_string(),
                usage: "get attr <selector> <attribute>",
            })?;
            let attr = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "get attr".to_string(),
                usage: "get attr <selector> <attribute>",
            })?;
            Ok(json!({ "id": id, "action": "getattribute", "selector": sel, "attribute": attr }))
        }
        Some("url") => Ok(json!({ "id": id, "action": "url" })),
        Some("cdp-url") => Ok(json!({ "id": id, "action": "cdp_url" })),
        Some("title") => Ok(json!({ "id": id, "action": "title" })),
        Some("count") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get count".to_string(),
                usage: "get count <selector>",
            })?;
            Ok(json!({ "id": id, "action": "count", "selector": sel }))
        }
        Some("box") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get box".to_string(),
                usage: "get box <selector>",
            })?;
            Ok(json!({ "id": id, "action": "boundingbox", "selector": sel }))
        }
        Some("styles") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "get styles".to_string(),
                usage: "get styles <selector>",
            })?;
            Ok(json!({ "id": id, "action": "styles", "selector": sel }))
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "get".to_string(),
            usage: "get <text|html|value|attr|url|title|count|box|styles|cdp-url> [args...]",
        }),
    }
}

fn parse_is(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &["visible", "enabled", "checked"];

    match rest.first().copied() {
        Some("visible") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "is visible".to_string(),
                usage: "is visible <selector>",
            })?;
            Ok(json!({ "id": id, "action": "isvisible", "selector": sel }))
        }
        Some("enabled") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "is enabled".to_string(),
                usage: "is enabled <selector>",
            })?;
            Ok(json!({ "id": id, "action": "isenabled", "selector": sel }))
        }
        Some("checked") => {
            let sel = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "is checked".to_string(),
                usage: "is checked <selector>",
            })?;
            Ok(json!({ "id": id, "action": "ischecked", "selector": sel }))
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "is".to_string(),
            usage: "is <visible|enabled|checked> <selector>",
        }),
    }
}

fn parse_find(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &[
        "role",
        "text",
        "label",
        "placeholder",
        "alt",
        "title",
        "testid",
        "first",
        "last",
        "nth",
    ];

    let locator = rest.first().ok_or_else(|| ParseError::MissingArguments {
        context: "find".to_string(),
        usage: "find <locator> <value> [action] [text]",
    })?;

    match *locator {
        "role" | "text" | "label" | "placeholder" | "alt" | "title" | "testid" | "first"
        | "last" => {
            let value = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: format!("find {}", locator),
                usage: match *locator {
                    "role" => "find role <role> [action] [--name <name>] [--exact]",
                    "text" => "find text <text> [action] [--exact]",
                    "label" => "find label <label> [action] [text] [--exact]",
                    "placeholder" => "find placeholder <text> [action] [text] [--exact]",
                    "alt" => "find alt <text> [action] [--exact]",
                    "title" => "find title <text> [action] [--exact]",
                    "testid" => "find testid <id> [action] [text]",
                    "first" => "find first <selector> [action] [text]",
                    "last" => "find last <selector> [action] [text]",
                    _ => "find <locator> <value> [action] [text]",
                },
            })?;
            let raw_subaction = rest.get(2).copied();
            if let Some(s) = raw_subaction {
                if s.starts_with("--") {
                    return Err(ParseError::InvalidValue {
                        message: format!(
                            "Missing action verb for `find {locator}` (got `{flag}` where action was expected).\n\
                             Valid actions: click, fill, check, hover, text\n\
                             Did you mean: agent-browser find {locator} <value> click {flag} ...?",
                            locator = locator,
                            flag = s,
                        ),
                        usage: match *locator {
                            "role" => "find role <role> <action> [--name <name>] [--exact]",
                            "text" => "find text <text> <action> [--exact]",
                            "label" => "find label <label> <action> [text] [--exact]",
                            "placeholder" => "find placeholder <text> <action> [text] [--exact]",
                            "alt" => "find alt <text> <action> [--exact]",
                            "title" => "find title <text> <action> [--exact]",
                            "testid" => "find testid <id> <action> [text]",
                            "first" => "find first <selector> <action> [text]",
                            "last" => "find last <selector> <action> [text]",
                            _ => "find <locator> <value> <action> [text]",
                        },
                    });
                }
            }
            let subaction = raw_subaction.unwrap_or("click");
            let mut name: Option<&str> = None;
            let mut exact = false;
            let mut fill_parts: Vec<&str> = Vec::new();

            if rest.len() > 3 {
                let mut i = 3;
                while i < rest.len() {
                    match rest[i] {
                        "--exact" => {
                            exact = true;
                            i += 1;
                        }
                        "--name" => {
                            let n =
                                rest.get(i + 1)
                                    .ok_or_else(|| ParseError::MissingArguments {
                                        context: format!("find {}", locator),
                                        usage:
                                            "find role <role> [action] [--name <name>] [--exact]",
                                    })?;
                            name = Some(*n);
                            i += 2;
                        }
                        token => {
                            fill_parts.push(token);
                            i += 1;
                        }
                    }
                }
            }

            let fill_value = if fill_parts.is_empty() {
                None
            } else {
                Some(fill_parts.join(" "))
            };

            match *locator {
                "role" => {
                    let mut cmd = json!({ "id": id, "action": "getbyrole", "role": value, "subaction": subaction, "name": name, "exact": exact });
                    if let Some(v) = fill_value {
                        cmd["value"] = json!(v);
                    }
                    Ok(cmd)
                }
                "text" => Ok(
                    json!({ "id": id, "action": "getbytext", "text": value, "subaction": subaction, "exact": exact }),
                ),
                "label" => {
                    let mut cmd = json!({ "id": id, "action": "getbylabel", "label": value, "subaction": subaction, "exact": exact });
                    if let Some(v) = fill_value {
                        cmd["value"] = json!(v);
                    }
                    Ok(cmd)
                }
                "placeholder" => {
                    let mut cmd = json!({ "id": id, "action": "getbyplaceholder", "placeholder": value, "subaction": subaction, "exact": exact });
                    if let Some(v) = fill_value {
                        cmd["value"] = json!(v);
                    }
                    Ok(cmd)
                }
                "alt" => Ok(
                    json!({ "id": id, "action": "getbyalttext", "text": value, "subaction": subaction, "exact": exact }),
                ),
                "title" => Ok(
                    json!({ "id": id, "action": "getbytitle", "text": value, "subaction": subaction, "exact": exact }),
                ),
                "testid" => {
                    let mut cmd = json!({ "id": id, "action": "getbytestid", "testId": value, "subaction": subaction });
                    if let Some(v) = fill_value {
                        cmd["value"] = json!(v);
                    }
                    Ok(cmd)
                }
                "first" => {
                    let mut cmd = json!({ "id": id, "action": "nth", "selector": value, "index": 0, "subaction": subaction });
                    if let Some(v) = fill_value {
                        cmd["value"] = json!(v);
                    }
                    Ok(cmd)
                }
                "last" => {
                    let mut cmd = json!({ "id": id, "action": "nth", "selector": value, "index": -1, "subaction": subaction });
                    if let Some(v) = fill_value {
                        cmd["value"] = json!(v);
                    }
                    Ok(cmd)
                }
                _ => unreachable!(),
            }
        }
        "nth" => {
            let idx_str = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "find nth".to_string(),
                usage: "find nth <index> <selector> [action] [text]",
            })?;
            let idx = idx_str
                .parse::<i32>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "find nth".to_string(),
                    usage: "find nth <index> <selector> [action] [text]",
                })?;
            let sel = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "find nth".to_string(),
                usage: "find nth <index> <selector> [action] [text]",
            })?;
            let sub = rest.get(3).unwrap_or(&"click");
            let fv = if rest.len() > 4 {
                Some(rest[4..].join(" "))
            } else {
                None
            };
            let mut cmd = json!({ "id": id, "action": "nth", "selector": sel, "index": idx, "subaction": sub });
            if let Some(v) = fv {
                cmd["value"] = json!(v);
            }
            Ok(cmd)
        }
        _ => Err(ParseError::UnknownSubcommand {
            subcommand: locator.to_string(),
            valid_options: VALID,
        }),
    }
}

fn parse_mouse(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &["move", "down", "up", "wheel"];

    match rest.first().copied() {
        Some("move") => {
            let x_str = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "mouse move".to_string(),
                usage: "mouse move <x> <y>",
            })?;
            let y_str = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "mouse move".to_string(),
                usage: "mouse move <x> <y>",
            })?;
            let x = x_str
                .parse::<i32>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "mouse move".to_string(),
                    usage: "mouse move <x> <y>",
                })?;
            let y = y_str
                .parse::<i32>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "mouse move".to_string(),
                    usage: "mouse move <x> <y>",
                })?;
            Ok(json!({ "id": id, "action": "mousemove", "x": x, "y": y }))
        }
        Some("down") => {
            Ok(json!({ "id": id, "action": "mousedown", "button": rest.get(1).unwrap_or(&"left") }))
        }
        Some("up") => {
            Ok(json!({ "id": id, "action": "mouseup", "button": rest.get(1).unwrap_or(&"left") }))
        }
        Some("wheel") => {
            let dy = rest
                .get(1)
                .and_then(|s| s.parse::<i32>().ok())
                .unwrap_or(100);
            let dx = rest.get(2).and_then(|s| s.parse::<i32>().ok()).unwrap_or(0);
            Ok(json!({ "id": id, "action": "wheel", "deltaX": dx, "deltaY": dy }))
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "mouse".to_string(),
            usage: "mouse <move|down|up|wheel> [args...]",
        }),
    }
}

fn parse_set(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &[
        "viewport",
        "device",
        "geo",
        "geolocation",
        "offline",
        "headers",
        "credentials",
        "auth",
        "media",
    ];

    match rest.first().copied() {
        Some("viewport") => {
            let w_str = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "set viewport".to_string(),
                usage: "set viewport <width> <height> [scale]",
            })?;
            let h_str = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "set viewport".to_string(),
                usage: "set viewport <width> <height> [scale]",
            })?;
            let w = w_str
                .parse::<i32>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "set viewport".to_string(),
                    usage: "set viewport <width> <height> [scale]",
                })?;
            let h = h_str
                .parse::<i32>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "set viewport".to_string(),
                    usage: "set viewport <width> <height> [scale]",
                })?;
            let mut cmd = json!({ "id": id, "action": "viewport", "width": w, "height": h });
            if let Some(scale_str) = rest.get(3) {
                let scale = scale_str
                    .parse::<f64>()
                    .map_err(|_| ParseError::MissingArguments {
                        context: "set viewport".to_string(),
                        usage: "set viewport <width> <height> [scale]",
                    })?;
                cmd["deviceScaleFactor"] = json!(scale);
            }
            Ok(cmd)
        }
        Some("device") => {
            let dev = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "set device".to_string(),
                usage: "set device <name>",
            })?;
            Ok(json!({ "id": id, "action": "device", "device": dev }))
        }
        Some("geo") | Some("geolocation") => {
            let lat_str = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "set geo".to_string(),
                usage: "set geo <latitude> <longitude>",
            })?;
            let lng_str = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "set geo".to_string(),
                usage: "set geo <latitude> <longitude>",
            })?;
            let lat = lat_str
                .parse::<f64>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "set geo".to_string(),
                    usage: "set geo <latitude> <longitude>",
                })?;
            let lng = lng_str
                .parse::<f64>()
                .map_err(|_| ParseError::MissingArguments {
                    context: "set geo".to_string(),
                    usage: "set geo <latitude> <longitude>",
                })?;
            Ok(json!({ "id": id, "action": "geolocation", "latitude": lat, "longitude": lng }))
        }
        Some("offline") => {
            let off = rest
                .get(1)
                .map(|s| *s != "off" && *s != "false")
                .unwrap_or(true);
            Ok(json!({ "id": id, "action": "offline", "offline": off }))
        }
        Some("headers") => {
            let headers_json = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "set headers".to_string(),
                usage: "set headers <json>",
            })?;
            // Parse the JSON string into an object
            let headers: serde_json::Value =
                serde_json::from_str(headers_json).map_err(|_| ParseError::MissingArguments {
                    context: "set headers".to_string(),
                    usage: "set headers <json> (must be valid JSON object)",
                })?;
            Ok(json!({ "id": id, "action": "headers", "headers": headers }))
        }
        Some("credentials") | Some("auth") => {
            let user = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "set credentials".to_string(),
                usage: "set credentials <username> <password>",
            })?;
            let pass = rest.get(2).ok_or_else(|| ParseError::MissingArguments {
                context: "set credentials".to_string(),
                usage: "set credentials <username> <password>",
            })?;
            Ok(json!({ "id": id, "action": "credentials", "username": user, "password": pass }))
        }
        Some("media") => {
            let color = if rest.contains(&"dark") {
                "dark"
            } else if rest.contains(&"light") {
                "light"
            } else {
                "no-preference"
            };
            let reduced = if rest.contains(&"reduced-motion") {
                "reduce"
            } else {
                "no-preference"
            };
            Ok(
                json!({ "id": id, "action": "emulatemedia", "colorScheme": color, "reducedMotion": reduced }),
            )
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "set".to_string(),
            usage: "set <viewport|device|geo|offline|headers|credentials|media> [args...]",
        }),
    }
}

/// Parse network interception, request inspection, and HAR recording commands.
fn parse_network(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &["route", "unroute", "requests", "request", "har"];

    match rest.first().copied() {
        Some("route") => {
            let url = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "network route".to_string(),
                usage: "network route <url> [--abort|--body <json>] [--resource-type <csv>]",
            })?;
            let abort = rest.contains(&"--abort");
            let body_idx = rest.iter().position(|&s| s == "--body");
            let body = body_idx.and_then(|i| rest.get(i + 1).copied());
            let rt_idx = rest
                .iter()
                .position(|&s| s == "--resource-type" || s == "--resource-types");
            let resource_type = rt_idx.and_then(|i| rest.get(i + 1).copied());
            let mut cmd =
                json!({ "id": id, "action": "route", "url": url, "abort": abort, "body": body });
            if let Some(rt) = resource_type {
                cmd["resourceType"] = json!(rt);
            }
            Ok(cmd)
        }
        Some("unroute") => {
            let mut cmd = json!({ "id": id, "action": "unroute" });
            if let Some(url) = rest.get(1) {
                cmd["url"] = json!(url);
            }
            Ok(cmd)
        }
        Some("requests") => {
            let clear = rest.contains(&"--clear");
            let filter_idx = rest.iter().position(|&s| s == "--filter");
            let filter = filter_idx.and_then(|i| rest.get(i + 1).copied());
            let type_idx = rest.iter().position(|&s| s == "--type");
            let rtype = type_idx.and_then(|i| rest.get(i + 1).copied());
            let method_idx = rest.iter().position(|&s| s == "--method");
            let method = method_idx.and_then(|i| rest.get(i + 1).copied());
            let status_idx = rest.iter().position(|&s| s == "--status");
            let status = status_idx.and_then(|i| rest.get(i + 1).copied());
            let mut cmd = json!({ "id": id, "action": "requests", "clear": clear });
            if let Some(f) = filter {
                cmd["filter"] = json!(f);
            }
            if let Some(t) = rtype {
                cmd["type"] = json!(t);
            }
            if let Some(m) = method {
                cmd["method"] = json!(m);
            }
            if let Some(s) = status {
                cmd["status"] = json!(s);
            }
            Ok(cmd)
        }
        Some("request") => {
            let request_id = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
                context: "network request".to_string(),
                usage: "network request <requestId>",
            })?;
            Ok(json!({ "id": id, "action": "request_detail", "requestId": request_id }))
        }
        Some("har") => {
            const HAR_VALID: &[&str] = &["start", "stop"];
            match rest.get(1).copied() {
                Some("start") => Ok(json!({ "id": id, "action": "har_start" })),
                Some("stop") => {
                    let mut cmd = json!({ "id": id, "action": "har_stop" });
                    if let Some(path) = rest.get(2) {
                        cmd["path"] = json!(path);
                    }
                    Ok(cmd)
                }
                Some(sub) => Err(ParseError::UnknownSubcommand {
                    subcommand: sub.to_string(),
                    valid_options: HAR_VALID,
                }),
                None => Err(ParseError::MissingArguments {
                    context: "network har".to_string(),
                    usage: "network har <start|stop> [path]",
                }),
            }
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "network".to_string(),
            usage: "network <route|unroute|requests|request|har> [args...]",
        }),
    }
}

fn parse_storage(rest: &[&str], id: &str) -> Result<Value, ParseError> {
    const VALID: &[&str] = &["local", "session"];

    match rest.first().copied() {
        Some("local") | Some("session") => {
            let storage_type = rest.first().unwrap();
            let (op, key, value) = match rest.get(1) {
                Some(&"get") => ("get", rest.get(2), rest.get(3)),
                Some(&"set") => ("set", rest.get(2), rest.get(3)),
                Some(&"clear") => ("clear", rest.get(2), rest.get(3)),
                Some(_) => ("get", rest.get(1), rest.get(2)),
                None => ("get", None, None),
            };
            match op {
                "set" => {
                    let k = key.ok_or_else(|| ParseError::MissingArguments {
                        context: format!("storage {} set", storage_type),
                        usage: "storage <local|session> set <key> <value>",
                    })?;
                    let v = value.ok_or_else(|| ParseError::MissingArguments {
                        context: format!("storage {} set", storage_type),
                        usage: "storage <local|session> set <key> <value>",
                    })?;
                    Ok(
                        json!({ "id": id, "action": "storage_set", "type": storage_type, "key": k, "value": v }),
                    )
                }
                "clear" => Ok(json!({ "id": id, "action": "storage_clear", "type": storage_type })),
                _ => {
                    let mut cmd =
                        json!({ "id": id, "action": "storage_get", "type": storage_type });
                    if let Some(k) = key {
                        cmd.as_object_mut()
                            .unwrap()
                            .insert("key".to_string(), json!(k));
                    }
                    Ok(cmd)
                }
            }
        }
        Some(sub) => Err(ParseError::UnknownSubcommand {
            subcommand: sub.to_string(),
            valid_options: VALID,
        }),
        None => Err(ParseError::MissingArguments {
            context: "storage".to_string(),
            usage: "storage <local|session> [get|set|clear] [key] [value]",
        }),
    }
}

/// Split a string into arguments respecting shell quoting (double/single quotes, backslash escapes).
pub fn shell_words_split(s: &str) -> Vec<String> {
    let mut args = Vec::new();
    let mut current = String::new();
    let mut in_double = false;
    let mut in_single = false;
    let mut chars = s.chars().peekable();

    while let Some(c) = chars.next() {
        match c {
            '\\' if !in_single => {
                if let Some(&next) = chars.peek() {
                    chars.next();
                    current.push(next);
                }
            }
            '"' if !in_single => in_double = !in_double,
            '\'' if !in_double => in_single = !in_single,
            ' ' if !in_double && !in_single => {
                if !current.is_empty() {
                    args.push(std::mem::take(&mut current));
                }
            }
            _ => current.push(c),
        }
    }
    if !current.is_empty() {
        args.push(current);
    }
    args
}

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

    fn default_flags() -> Flags {
        Flags {
            session: "test".to_string(),
            json: false,
            headed: false,
            debug: false,
            headers: None,
            executable_path: None,
            extensions: Vec::new(),
            init_scripts: Vec::new(),
            enable: Vec::new(),
            cdp: None,
            profile: None,
            state: None,
            proxy: None,
            proxy_bypass: None,
            args: None,
            user_agent: None,
            provider: None,
            ignore_https_errors: false,
            allow_file_access: false,
            hide_scrollbars: true,
            device: None,
            auto_connect: false,
            force_launch: false,
            session_name: None,
            cli_executable_path: false,
            cli_extensions: false,
            cli_init_scripts: false,
            cli_enable: false,
            cli_profile: false,
            cli_state: false,
            cli_args: false,
            cli_user_agent: false,
            cli_proxy: false,
            cli_proxy_bypass: false,
            cli_allow_file_access: false,
            cli_hide_scrollbars: false,
            cli_annotate: false,
            cli_download_path: false,
            cli_headed: false,
            annotate: false,
            color_scheme: None,
            download_path: None,
            content_boundaries: false,
            max_output: None,
            allowed_domains: None,
            action_policy: None,
            confirm_actions: None,
            confirm_interactive: false,
            engine: None,
            screenshot_dir: None,
            screenshot_quality: None,
            screenshot_format: None,
            idle_timeout: None,
            default_timeout: None,
            no_auto_dialog: false,
            model: None,
            verbose: false,
            quiet: false,
        }
    }

    fn args(s: &str) -> Vec<String> {
        s.split_whitespace().map(String::from).collect()
    }

    // === Cookies Tests ===

    #[test]
    fn test_cookies_get() {
        let cmd = parse_command(&args("cookies"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cookies_get");
    }

    #[test]
    fn test_cookies_get_explicit() {
        let cmd = parse_command(&args("cookies get"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cookies_get");
    }

    #[test]
    fn test_cookies_set() {
        let cmd = parse_command(&args("cookies set mycookie myvalue"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
    }

    #[test]
    fn test_cookies_set_missing_value() {
        let result = parse_command(&args("cookies set mycookie"), &default_flags());
        assert!(result.is_err());
    }

    #[test]
    fn test_cookies_clear() {
        let cmd = parse_command(&args("cookies clear"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cookies_clear");
    }

    #[test]
    fn test_parse_curl_cookies_json_preserves_attributes() {
        // A full cookie export (httpOnly session token, per-domain, secure,
        // sameSite, expiry) must round-trip — not get flattened to name/value.
        let input = r#"[
            {"name":"__Secure-next-auth.session-token","value":"eyJ.tok","domain":".chatgpt.com","path":"/","secure":true,"httpOnly":true,"sameSite":"Lax","expires":1893456000},
            {"name":"cf_clearance","value":"abc","domain":".openai.com","path":"/","secure":true,"http_only":true,"same_site":"no_restriction"}
        ]"#;
        let out = parse_curl_cookies(input).unwrap();
        assert_eq!(out.len(), 2);
        assert_eq!(out[0]["domain"], ".chatgpt.com");
        assert_eq!(out[0]["secure"], true);
        assert_eq!(out[0]["httpOnly"], true);
        assert_eq!(out[0]["sameSite"], "Lax");
        assert_eq!(out[0]["expires"], 1893456000.0);
        // alias keys (http_only, same_site=no_restriction) normalize to CDP shape
        assert_eq!(out[1]["domain"], ".openai.com");
        assert_eq!(out[1]["httpOnly"], true);
        assert_eq!(out[1]["sameSite"], "None");
    }

    #[test]
    fn test_parse_curl_cookies_json_array() {
        let input = r#"[{"name":"a","value":"1"},{"name":"b","value":"2"}]"#;
        let out = parse_curl_cookies(input).unwrap();
        assert_eq!(out.len(), 2);
        assert_eq!(out[0]["name"], "a");
        assert_eq!(out[0]["value"], "1");
        // bare {name,value} stays minimal — no spurious attribute keys
        assert!(out[0].get("domain").is_none());
        assert!(out[0].get("secure").is_none());
        assert_eq!(out[1]["name"], "b");
        assert_eq!(out[1]["value"], "2");
    }

    #[test]
    fn test_parse_curl_cookies_bare_header() {
        let input = "sid=abc; token=xyz; other=";
        let out = parse_curl_cookies(input).unwrap();
        assert_eq!(out.len(), 3);
        assert_eq!(out[0]["name"], "sid");
        assert_eq!(out[0]["value"], "abc");
        assert_eq!(out[2]["name"], "other");
        assert_eq!(out[2]["value"], "");
    }

    #[test]
    fn test_parse_curl_cookies_from_curl_bash() {
        let input = "curl 'https://example.com/api' \\\n  -H 'accept: application/json' \\\n  -H 'cookie: sid=abc; token=xyz'";
        let out = parse_curl_cookies(input).unwrap();
        assert_eq!(out.len(), 2);
        assert_eq!(out[0]["name"], "sid");
        assert_eq!(out[1]["name"], "token");
    }

    #[test]
    fn test_parse_curl_cookies_from_curl_b_flag() {
        let input = "curl 'https://example.com/api' -b 'sid=abc; token=xyz'";
        let out = parse_curl_cookies(input).unwrap();
        assert_eq!(out.len(), 2);
        assert_eq!(out[0]["name"], "sid");
    }

    #[test]
    fn test_parse_curl_cookies_empty_error() {
        assert!(parse_curl_cookies("").is_err());
        assert!(parse_curl_cookies("   ").is_err());
    }

    #[test]
    fn test_parse_curl_cookies_never_echoes_values_in_errors() {
        // Error messages must never echo cookie values or any bytes from the
        // input that could be a secret. Use a distinct recognizable token per
        // case so a regression surfaces the leaking code path clearly.

        // JSON array with a missing `name` field.
        let name_secret = "LEAK_VIA_MISSING_NAME_xY9zQ";
        let input = format!(r#"[{{"value":"{}"}}]"#, name_secret);
        let err = parse_curl_cookies(&input).unwrap_err();
        assert!(
            !err.contains(name_secret),
            "missing-name error leaked secret value: {}",
            err
        );

        // Truncated / malformed JSON containing a secret. Depends on
        // serde_json's Display impl to report position only, not payload.
        let parse_secret = "LEAK_VIA_JSON_PARSE_aA1bB2";
        let input = format!(r#"[{{"name":"sid","value":"{}"#, parse_secret);
        let err = parse_curl_cookies(&input).unwrap_err();
        assert!(
            !err.contains(parse_secret),
            "malformed-JSON error leaked secret value: {}",
            err
        );

        // Valid cURL dump with no Cookie header but a secret in another
        // header. Should error on "no Cookie header found" without echoing.
        let curl_secret = "LEAK_VIA_CURL_NO_COOKIE_pP7qQ";
        let input = format!("curl 'https://example.com/' -H 'x-token: {}'", curl_secret);
        let err = parse_curl_cookies(&input).unwrap_err();
        assert!(
            !err.contains(curl_secret),
            "cURL-without-cookie error leaked secret value: {}",
            err
        );
    }

    #[test]
    fn test_react_tree_command() {
        let cmd = parse_command(&args("react tree"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "react_tree");
    }

    #[test]
    fn test_react_tree_json() {
        let cmd = parse_command(&args("react tree --json"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "react_tree");
        assert_eq!(cmd["json"], true);
    }

    #[test]
    fn test_react_inspect_command() {
        let cmd = parse_command(&args("react inspect 12345"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "react_inspect");
        assert_eq!(cmd["fiberId"], 12345);
    }

    #[test]
    fn test_react_inspect_requires_numeric_id() {
        let err = parse_command(&args("react inspect not-a-number"), &default_flags());
        assert!(err.is_err());
    }

    #[test]
    fn test_react_renders_start_stop() {
        let start = parse_command(&args("react renders start"), &default_flags()).unwrap();
        assert_eq!(start["action"], "react_renders_start");
        let stop = parse_command(&args("react renders stop"), &default_flags()).unwrap();
        assert_eq!(stop["action"], "react_renders_stop");
        let stop_json =
            parse_command(&args("react renders stop --json"), &default_flags()).unwrap();
        assert_eq!(stop_json["json"], true);
    }

    #[test]
    fn test_react_suspense() {
        let cmd = parse_command(&args("react suspense"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "react_suspense");
        // onlyDynamic defaults to unset; absence means the full report
        assert!(cmd.get("onlyDynamic").is_none());
    }

    #[test]
    fn test_react_suspense_only_dynamic() {
        let cmd = parse_command(&args("react suspense --only-dynamic"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "react_suspense");
        assert_eq!(cmd["onlyDynamic"], true);
    }

    #[test]
    fn test_react_suspense_only_dynamic_with_json() {
        let cmd = parse_command(
            &args("react suspense --only-dynamic --json"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "react_suspense");
        assert_eq!(cmd["onlyDynamic"], true);
        assert_eq!(cmd["json"], true);
    }

    #[test]
    fn test_vitals_command() {
        let cmd = parse_command(&args("vitals"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "vitals");
        let cmd = parse_command(
            &args("vitals http://localhost:3000/dashboard"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["url"], "http://localhost:3000/dashboard");
    }

    #[test]
    fn test_pushstate_command() {
        let cmd = parse_command(&args("pushstate /foo"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "pushstate");
        assert_eq!(cmd["url"], "/foo");
    }

    #[test]
    fn test_removeinitscript_command() {
        let cmd = parse_command(&args("removeinitscript abc123"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "removeinitscript");
        assert_eq!(cmd["identifier"], "abc123");
    }

    #[test]
    fn test_network_route_resource_type() {
        let cmd = parse_command(
            &args("network route * --abort --resource-type script"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "route");
        assert_eq!(cmd["resourceType"], "script");
        assert_eq!(cmd["abort"], true);
    }

    #[test]
    fn test_cookies_set_with_url() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --url https://example.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["url"], "https://example.com");
    }

    #[test]
    fn test_cookies_set_with_domain() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --domain example.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["domain"], "example.com");
    }

    #[test]
    fn test_cookies_set_with_path() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --path /api"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["path"], "/api");
    }

    #[test]
    fn test_cookies_set_with_httponly() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --httpOnly"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["httpOnly"], true);
    }

    #[test]
    fn test_cookies_set_with_secure() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --secure"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["secure"], true);
    }

    #[test]
    fn test_cookies_set_with_samesite() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --sameSite Strict"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["sameSite"], "Strict");
    }

    #[test]
    fn test_cookies_set_with_expires() {
        let cmd = parse_command(
            &args("cookies set mycookie myvalue --expires 1234567890"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["expires"], 1234567890);
    }

    #[test]
    fn test_cookies_set_with_multiple_flags() {
        let cmd = parse_command(&args("cookies set mycookie myvalue --url https://example.com --httpOnly --secure --sameSite Lax"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["url"], "https://example.com");
        assert_eq!(cmd["cookies"][0]["httpOnly"], true);
        assert_eq!(cmd["cookies"][0]["secure"], true);
        assert_eq!(cmd["cookies"][0]["sameSite"], "Lax");
    }

    #[test]
    fn test_cookies_set_with_all_flags() {
        let cmd = parse_command(&args("cookies set mycookie myvalue --url https://example.com --domain example.com --path /api --httpOnly --secure --sameSite None --expires 9999999999"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cookies_set");
        assert_eq!(cmd["cookies"][0]["name"], "mycookie");
        assert_eq!(cmd["cookies"][0]["value"], "myvalue");
        assert_eq!(cmd["cookies"][0]["url"], "https://example.com");
        assert_eq!(cmd["cookies"][0]["domain"], "example.com");
        assert_eq!(cmd["cookies"][0]["path"], "/api");
        assert_eq!(cmd["cookies"][0]["httpOnly"], true);
        assert_eq!(cmd["cookies"][0]["secure"], true);
        assert_eq!(cmd["cookies"][0]["sameSite"], "None");
        assert_eq!(cmd["cookies"][0]["expires"], 9999999999i64);
    }

    #[test]
    fn test_cookies_set_invalid_samesite() {
        let result = parse_command(
            &args("cookies set mycookie myvalue --sameSite Invalid"),
            &default_flags(),
        );
        assert!(result.is_err());
    }

    // === Storage Tests ===

    #[test]
    fn test_storage_local_get() {
        let cmd = parse_command(&args("storage local"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_get");
        assert_eq!(cmd["type"], "local");
        assert!(cmd.get("key").is_none());
    }

    #[test]
    fn test_storage_local_get_key() {
        let cmd = parse_command(&args("storage local get mykey"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_get");
        assert_eq!(cmd["type"], "local");
        assert_eq!(cmd["key"], "mykey");
    }

    #[test]
    fn test_storage_local_get_implicit_key() {
        let cmd = parse_command(&args("storage local mykey"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_get");
        assert_eq!(cmd["type"], "local");
        assert_eq!(cmd["key"], "mykey");
    }

    #[test]
    fn test_storage_session_get() {
        let cmd = parse_command(&args("storage session"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_get");
        assert_eq!(cmd["type"], "session");
    }

    #[test]
    fn test_storage_session_get_implicit_key() {
        let cmd = parse_command(&args("storage session mykey"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_get");
        assert_eq!(cmd["type"], "session");
        assert_eq!(cmd["key"], "mykey");
    }

    #[test]
    fn test_storage_local_set() {
        let cmd =
            parse_command(&args("storage local set mykey myvalue"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_set");
        assert_eq!(cmd["type"], "local");
        assert_eq!(cmd["key"], "mykey");
        assert_eq!(cmd["value"], "myvalue");
    }

    #[test]
    fn test_storage_session_set() {
        let cmd =
            parse_command(&args("storage session set skey svalue"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_set");
        assert_eq!(cmd["type"], "session");
        assert_eq!(cmd["key"], "skey");
        assert_eq!(cmd["value"], "svalue");
    }

    #[test]
    fn test_storage_set_missing_value() {
        let result = parse_command(&args("storage local set mykey"), &default_flags());
        assert!(result.is_err());
    }

    #[test]
    fn test_storage_local_clear() {
        let cmd = parse_command(&args("storage local clear"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_clear");
        assert_eq!(cmd["type"], "local");
    }

    #[test]
    fn test_storage_session_clear() {
        let cmd = parse_command(&args("storage session clear"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "storage_clear");
        assert_eq!(cmd["type"], "session");
    }

    #[test]
    fn test_storage_invalid_type() {
        let result = parse_command(&args("storage invalid"), &default_flags());
        assert!(result.is_err());
    }

    // === Navigation Tests ===

    #[test]
    fn test_open_without_url_launches() {
        let cmd = parse_command(&args("open"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["headless"], true);
    }

    #[test]
    fn test_open_without_url_headed() {
        let mut flags = default_flags();
        flags.headed = true;
        let cmd = parse_command(&args("open"), &flags).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["headless"], false);
    }

    #[test]
    fn test_goto_still_requires_url() {
        assert!(parse_command(&args("goto"), &default_flags()).is_err());
        assert!(parse_command(&args("navigate"), &default_flags()).is_err());
    }

    #[test]
    fn test_navigate_with_https() {
        let cmd = parse_command(&args("open https://example.com"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "navigate");
        assert_eq!(cmd["url"], "https://example.com");
    }

    #[test]
    fn test_navigate_without_protocol() {
        let cmd = parse_command(&args("open example.com"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "navigate");
        assert_eq!(cmd["url"], "https://example.com");
    }

    #[test]
    fn test_navigate_with_headers() {
        let mut flags = default_flags();
        flags.headers = Some(r#"{"Authorization": "Bearer token"}"#.to_string());
        let cmd = parse_command(&args("open api.example.com"), &flags).unwrap();
        assert_eq!(cmd["action"], "navigate");
        assert_eq!(cmd["url"], "https://api.example.com");
        assert_eq!(cmd["headers"]["Authorization"], "Bearer token");
    }

    #[test]
    fn test_navigate_with_multiple_headers() {
        let mut flags = default_flags();
        flags.headers =
            Some(r#"{"Authorization": "Bearer token", "X-Custom": "value"}"#.to_string());
        let cmd = parse_command(&args("open api.example.com"), &flags).unwrap();
        assert_eq!(cmd["headers"]["Authorization"], "Bearer token");
        assert_eq!(cmd["headers"]["X-Custom"], "value");
    }

    #[test]
    fn test_navigate_without_headers_flag() {
        let cmd = parse_command(&args("open example.com"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "navigate");
        // headers should not be present when flag is not set
        assert!(cmd.get("headers").is_none());
    }

    #[test]
    fn test_navigate_with_invalid_headers_json() {
        let mut flags = default_flags();
        flags.headers = Some("not valid json".to_string());
        let result = parse_command(&args("open api.example.com"), &flags);
        // Invalid JSON should return a ParseError, not silently drop headers
        assert!(result.is_err());
        let err = result.unwrap_err();
        let msg = err.format();
        assert!(msg.contains("Invalid JSON for --headers"));
    }

    #[test]
    fn test_navigate_chrome_extension_url() {
        let cmd = parse_command(
            &args("open chrome-extension://abcdefghijklmnop/popup.html"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "navigate");
        assert_eq!(cmd["url"], "chrome-extension://abcdefghijklmnop/popup.html");
    }

    #[test]
    fn test_navigate_chrome_url() {
        let cmd = parse_command(&args("open chrome://extensions"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "navigate");
        assert_eq!(cmd["url"], "chrome://extensions");
    }

    // === Set Headers Tests ===

    #[test]
    fn test_set_headers_parses_json() {
        let input: Vec<String> = vec![
            "set".to_string(),
            "headers".to_string(),
            r#"{"Authorization":"Bearer token"}"#.to_string(),
        ];
        let cmd = parse_command(&input, &default_flags()).unwrap();
        assert_eq!(cmd["action"], "headers");
        // Headers should be an object, not a string
        assert!(cmd["headers"].is_object());
        assert_eq!(cmd["headers"]["Authorization"], "Bearer token");
    }

    #[test]
    fn test_set_headers_with_multiple_values() {
        let input: Vec<String> = vec![
            "set".to_string(),
            "headers".to_string(),
            r#"{"Authorization": "Bearer token", "X-Custom": "value"}"#.to_string(),
        ];
        let cmd = parse_command(&input, &default_flags()).unwrap();
        assert_eq!(cmd["headers"]["Authorization"], "Bearer token");
        assert_eq!(cmd["headers"]["X-Custom"], "value");
    }

    #[test]
    fn test_set_headers_invalid_json_error() {
        let input: Vec<String> = vec![
            "set".to_string(),
            "headers".to_string(),
            "not-valid-json".to_string(),
        ];
        let result = parse_command(&input, &default_flags());
        assert!(result.is_err());
    }

    #[test]
    fn test_back() {
        let cmd = parse_command(&args("back"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "back");
    }

    #[test]
    fn test_forward() {
        let cmd = parse_command(&args("forward"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "forward");
    }

    #[test]
    fn test_reload() {
        let cmd = parse_command(&args("reload"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "reload");
    }

    // === Core Actions ===

    #[test]
    fn test_click() {
        let cmd = parse_command(&args("click #button"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "click");
        assert_eq!(cmd["selector"], "#button");
    }

    #[test]
    fn test_fill() {
        let cmd = parse_command(&args("fill #input hello world"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "fill");
        assert_eq!(cmd["selector"], "#input");
        assert_eq!(cmd["value"], "hello world");
    }

    #[test]
    fn test_type_command() {
        let cmd = parse_command(&args("type #input some text"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "type");
        assert_eq!(cmd["selector"], "#input");
        assert_eq!(cmd["text"], "some text");
    }

    #[test]
    fn test_select() {
        let cmd = parse_command(&args("select #menu option1"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "select");
        assert_eq!(cmd["selector"], "#menu");
        assert_eq!(cmd["values"], "option1");
    }

    #[test]
    fn test_select_multiple_values() {
        let cmd = parse_command(&args("select #menu opt1 opt2 opt3"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "select");
        assert_eq!(cmd["selector"], "#menu");
        assert_eq!(cmd["values"], json!(["opt1", "opt2", "opt3"]));
    }

    #[test]
    fn test_frame_main() {
        let cmd = parse_command(&args("frame main"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "mainframe");
    }

    // === Tabs ===

    #[test]
    fn test_tab_new() {
        let cmd = parse_command(&args("tab new"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_new");
        assert!(
            cmd.get("url").is_none(),
            "url should not be present when not provided"
        );
    }

    #[test]
    fn test_tab_new_with_url() {
        let cmd = parse_command(&args("tab new https://example.com"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_new");
        assert_eq!(cmd["url"], "https://example.com");
    }

    #[test]
    fn test_tab_list() {
        let cmd = parse_command(&args("tab list"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_list");
    }

    #[test]
    fn test_tab_switch_by_id() {
        let cmd = parse_command(&args("tab t2"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_switch");
        assert_eq!(cmd["tabId"], "t2");
    }

    #[test]
    fn test_tab_switch_by_label() {
        let cmd = parse_command(&args("tab docs"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_switch");
        assert_eq!(cmd["tabId"], "docs");
    }

    #[test]
    fn test_tab_close() {
        let cmd = parse_command(&args("tab close"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_close");
    }

    #[test]
    fn test_tab_close_with_id() {
        let cmd = parse_command(&args("tab close t2"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_close");
        assert_eq!(cmd["tabId"], "t2");
    }

    #[test]
    fn test_tab_close_with_label() {
        let cmd = parse_command(&args("tab close docs"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_close");
        assert_eq!(cmd["tabId"], "docs");
    }

    #[test]
    fn test_tab_sends_string_tab_id() {
        let cmd = parse_command(&args("tab t2"), &default_flags()).unwrap();
        assert!(
            cmd["tabId"].is_string(),
            "tabId must be a string, got: {:?}",
            cmd["tabId"]
        );
        assert!(cmd.get("index").is_none());
    }

    #[test]
    fn test_tab_new_with_label() {
        let cmd = parse_command(&args("tab new --label docs"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_new");
        assert_eq!(cmd["label"], "docs");
    }

    #[test]
    fn test_tab_new_with_label_and_url() {
        let cmd = parse_command(
            &args("tab new --label docs https://docs.example.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "tab_new");
        assert_eq!(cmd["label"], "docs");
        assert_eq!(cmd["url"], "https://docs.example.com");
    }

    #[test]
    fn test_tab_new_with_url_then_label() {
        let cmd = parse_command(
            &args("tab new https://docs.example.com --label docs"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["url"], "https://docs.example.com");
        assert_eq!(cmd["label"], "docs");
    }

    #[test]
    fn test_tab_no_args_defaults_to_list() {
        let cmd = parse_command(&args("tab"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_list");
    }

    #[test]
    fn test_tab_unknown_flag_errors() {
        // Unknown flags on `tab new` must error instead of being silently
        // dropped. This protects against typos like `--labl` or `--new-tab`.
        let result = parse_command(
            &args("tab new --unknown-flag https://example.com"),
            &default_flags(),
        );
        assert!(
            result.is_err(),
            "tab new with an unknown flag must error, got: {:?}",
            result
        );
    }

    #[test]
    fn test_tab_non_keyword_treated_as_ref() {
        // After the shift to `t<N>`/label ids, non-keyword tokens (`select`,
        // `docs`, etc.) are valid label refs; `tab <something>` routes to
        // tab_switch and the runtime decides whether the label exists.
        let cmd = parse_command(&args("tab select"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "tab_switch");
        assert_eq!(cmd["tabId"], "select");
    }

    // === Network ===

    #[test]
    fn test_network_har_start() {
        let cmd = parse_command(&args("network har start"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "har_start");
    }

    #[test]
    fn test_network_har_stop_with_path() {
        let cmd = parse_command(&args("network har stop ./capture.har"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "har_stop");
        assert_eq!(cmd["path"], "./capture.har");
    }

    #[test]
    fn test_network_har_stop_without_path() {
        let cmd = parse_command(&args("network har stop"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "har_stop");
        assert!(cmd.get("path").is_none());
    }

    #[test]
    fn test_network_har_requires_subcommand() {
        let result = parse_command(&args("network har"), &default_flags());
        assert!(matches!(result, Err(ParseError::MissingArguments { .. })));
    }

    #[test]
    fn test_network_requests_type_filter() {
        let cmd =
            parse_command(&args("network requests --type xhr,fetch"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "requests");
        assert_eq!(cmd["type"], "xhr,fetch");
    }

    #[test]
    fn test_network_requests_method_filter() {
        let cmd = parse_command(&args("network requests --method POST"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "requests");
        assert_eq!(cmd["method"], "POST");
    }

    #[test]
    fn test_network_requests_status_filter() {
        let cmd = parse_command(&args("network requests --status 2xx"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "requests");
        assert_eq!(cmd["status"], "2xx");
    }

    #[test]
    fn test_network_requests_combined_filters() {
        let cmd = parse_command(
            &args("network requests --filter api --type xhr --method GET --status 200"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["filter"], "api");
        assert_eq!(cmd["type"], "xhr");
        assert_eq!(cmd["method"], "GET");
        assert_eq!(cmd["status"], "200");
    }

    #[test]
    fn test_network_request_detail() {
        let cmd = parse_command(&args("network request 1234.5"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "request_detail");
        assert_eq!(cmd["requestId"], "1234.5");
    }

    #[test]
    fn test_network_request_detail_requires_id() {
        let result = parse_command(&args("network request"), &default_flags());
        assert!(matches!(result, Err(ParseError::MissingArguments { .. })));
    }

    // === Screenshot ===

    #[test]
    fn test_screenshot() {
        let cmd = parse_command(&args("screenshot"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["path"], serde_json::Value::Null);
        assert_eq!(cmd["selector"], serde_json::Value::Null);
    }

    #[test]
    fn test_screenshot_path() {
        let cmd = parse_command(&args("screenshot out.png"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["path"], "out.png");
    }

    #[test]
    fn test_screenshot_full_page() {
        let cmd = parse_command(&args("screenshot --full"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_screenshot_full_page_shorthand() {
        let cmd = parse_command(&args("screenshot -f"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_screenshot_with_ref() {
        let cmd = parse_command(&args("screenshot @e1"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["selector"], "@e1");
        assert_eq!(cmd["path"], serde_json::Value::Null);
    }

    #[test]
    fn test_screenshot_with_css_class() {
        let cmd = parse_command(&args("screenshot .my-button"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["selector"], ".my-button");
        assert_eq!(cmd["path"], serde_json::Value::Null);
    }

    #[test]
    fn test_screenshot_with_css_id() {
        let cmd = parse_command(&args("screenshot #header"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["selector"], "#header");
        assert_eq!(cmd["path"], serde_json::Value::Null);
    }

    #[test]
    fn test_screenshot_with_path() {
        let cmd = parse_command(&args("screenshot ./output.png"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["selector"], serde_json::Value::Null);
        assert_eq!(cmd["path"], "./output.png");
    }

    #[test]
    fn test_screenshot_with_selector_and_path() {
        let cmd = parse_command(&args("screenshot .btn ./button.png"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "screenshot");
        assert_eq!(cmd["selector"], ".btn");
        assert_eq!(cmd["path"], "./button.png");
    }

    // === Snapshot ===

    #[test]
    fn test_snapshot() {
        let cmd = parse_command(&args("snapshot"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
    }

    #[test]
    fn test_snapshot_interactive() {
        let cmd = parse_command(&args("snapshot -i"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["interactive"], true);
    }

    #[test]
    fn test_snapshot_cursor() {
        let cmd = parse_command(&args("snapshot -C"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["cursor"], true);
    }

    #[test]
    fn test_snapshot_interactive_cursor() {
        let cmd = parse_command(&args("snapshot -i -C"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["interactive"], true);
        assert_eq!(cmd["cursor"], true);
    }

    #[test]
    fn test_snapshot_compact() {
        let cmd = parse_command(&args("snapshot --compact"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["compact"], true);
    }

    #[test]
    fn test_snapshot_depth() {
        let cmd = parse_command(&args("snapshot -d 3"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["maxDepth"], 3);
    }

    #[test]
    fn test_snapshot_urls() {
        let cmd = parse_command(&args("snapshot -i --urls"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["interactive"], true);
        assert_eq!(cmd["urls"], true);
    }

    #[test]
    fn test_snapshot_urls_short() {
        let cmd = parse_command(&args("snapshot -i -u"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "snapshot");
        assert_eq!(cmd["urls"], true);
    }

    // === Wait ===

    #[test]
    fn test_wait_selector() {
        let cmd = parse_command(&args("wait #element"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["selector"], "#element");
    }

    #[test]
    fn test_wait_timeout() {
        let cmd = parse_command(&args("wait 5000"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["timeout"], 5000);
    }

    #[test]
    fn test_wait_url() {
        let cmd = parse_command(&args("wait --url **/dashboard"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitforurl");
        assert_eq!(cmd["url"], "**/dashboard");
    }

    #[test]
    fn test_wait_url_empty_pattern_rejected() {
        // An empty pattern would match any URL — reject it rather than silently
        // always-match. (Build argv directly: split_whitespace can't yield "".)
        let argv = vec!["wait".to_string(), "--url".to_string(), String::new()];
        let err = parse_command(&argv, &default_flags());
        assert!(err.is_err(), "empty --url pattern should be rejected");
    }

    #[test]
    fn test_wait_url_with_timeout() {
        // --timeout must be parsed for the --url path; without it a non-matching
        // pattern waits the full default (and could wedge the daemon).
        let cmd = parse_command(
            &args("wait --url **/dashboard --timeout 3000"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "waitforurl");
        assert_eq!(cmd["url"], "**/dashboard");
        assert_eq!(cmd["timeout"], 3000);
    }

    #[test]
    fn test_wait_load() {
        let cmd = parse_command(&args("wait --load networkidle"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitforloadstate");
        assert_eq!(cmd["state"], "networkidle");
    }

    #[test]
    fn test_wait_load_missing_state() {
        let result = parse_command(&args("wait --load"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_wait_fn() {
        let cmd = parse_command(&args("wait --fn window.ready"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitforfunction");
        assert_eq!(cmd["expression"], "window.ready");
    }

    #[test]
    fn test_wait_text() {
        let cmd = parse_command(&args("wait --text Welcome"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["text"], "Welcome");
        assert!(cmd.get("timeout").is_none());
    }

    #[test]
    fn test_wait_text_with_timeout() {
        let cmd = parse_command(
            &args("wait --text Welcome --timeout 5000"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["text"], "Welcome");
        assert_eq!(cmd["timeout"], 5000);
    }

    // === Clipboard Tests ===

    #[test]
    fn test_clipboard_read_default() {
        let cmd = parse_command(&args("clipboard"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "clipboard");
        assert_eq!(cmd["operation"], "read");
    }

    #[test]
    fn test_clipboard_read_explicit() {
        let cmd = parse_command(&args("clipboard read"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "clipboard");
        assert_eq!(cmd["operation"], "read");
    }

    #[test]
    fn test_clipboard_write() {
        let cmd = parse_command(&args("clipboard write hello"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "clipboard");
        assert_eq!(cmd["operation"], "write");
        assert_eq!(cmd["text"], "hello");
    }

    #[test]
    fn test_clipboard_write_multi_word() {
        let cmd = parse_command(&args("clipboard write hello world"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "clipboard");
        assert_eq!(cmd["operation"], "write");
        assert_eq!(cmd["text"], "hello world");
    }

    #[test]
    fn test_clipboard_copy() {
        let cmd = parse_command(&args("clipboard copy"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "clipboard");
        assert_eq!(cmd["operation"], "copy");
    }

    #[test]
    fn test_clipboard_paste() {
        let cmd = parse_command(&args("clipboard paste"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "clipboard");
        assert_eq!(cmd["operation"], "paste");
    }

    #[test]
    fn test_clipboard_write_missing_text() {
        let result = parse_command(&args("clipboard write"), &default_flags());
        assert!(result.is_err());
    }

    #[test]
    fn test_clipboard_unknown_subcommand() {
        let result = parse_command(&args("clipboard clear"), &default_flags());
        assert!(result.is_err());
    }

    // === Unknown command ===

    // === Record Tests ===

    #[test]
    fn test_record_start() {
        let cmd = parse_command(&args("record start output.webm"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "recording_start");
        assert_eq!(cmd["path"], "output.webm");
        assert!(cmd.get("url").is_none());
    }

    #[test]
    fn test_record_start_with_url() {
        let cmd = parse_command(
            &args("record start demo.webm https://example.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "recording_start");
        assert_eq!(cmd["path"], "demo.webm");
        assert_eq!(cmd["url"], "https://example.com");
    }

    #[test]
    fn test_record_start_with_url_no_protocol() {
        let cmd = parse_command(
            &args("record start demo.webm example.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "recording_start");
        assert_eq!(cmd["path"], "demo.webm");
        assert_eq!(cmd["url"], "https://example.com");
    }

    #[test]
    fn test_record_start_with_chrome_extension_url() {
        let cmd = parse_command(
            &args("record start demo.webm chrome-extension://abcdef/popup.html"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "recording_start");
        assert_eq!(cmd["path"], "demo.webm");
        assert_eq!(cmd["url"], "chrome-extension://abcdef/popup.html");
    }

    #[test]
    fn test_record_start_missing_path() {
        let result = parse_command(&args("record start"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_record_stop() {
        let cmd = parse_command(&args("record stop"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "recording_stop");
    }

    #[test]
    fn test_record_restart() {
        let cmd = parse_command(&args("record restart output.webm"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "recording_restart");
        assert_eq!(cmd["path"], "output.webm");
        assert!(cmd.get("url").is_none());
    }

    #[test]
    fn test_record_restart_with_url() {
        let cmd = parse_command(
            &args("record restart demo.webm https://example.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "recording_restart");
        assert_eq!(cmd["path"], "demo.webm");
        assert_eq!(cmd["url"], "https://example.com");
    }

    #[test]
    fn test_record_restart_missing_path() {
        let result = parse_command(&args("record restart"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_record_invalid_subcommand() {
        let result = parse_command(&args("record foo"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::UnknownSubcommand { .. }
        ));
    }

    #[test]
    fn test_record_missing_subcommand() {
        let result = parse_command(&args("record"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    // === Profile (CDP Tracing) Tests ===

    #[test]
    fn test_profiler_start() {
        let cmd = parse_command(&args("profiler start"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "profiler_start");
        assert!(cmd.get("categories").is_none());
    }

    #[test]
    fn test_profiler_start_with_categories() {
        let cmd = parse_command(
            &args("profiler start --categories devtools.timeline,v8.execute"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "profiler_start");
        let categories = cmd["categories"].as_array().unwrap();
        assert_eq!(categories.len(), 2);
        assert_eq!(categories[0], "devtools.timeline");
        assert_eq!(categories[1], "v8.execute");
    }

    #[test]
    fn test_profiler_start_categories_missing_value() {
        let result = parse_command(&args("profiler start --categories"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_profiler_stop_with_path() {
        let cmd = parse_command(&args("profiler stop trace.json"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "profiler_stop");
        assert_eq!(cmd["path"], "trace.json");
    }

    #[test]
    fn test_profiler_stop_no_path() {
        let cmd = parse_command(&args("profiler stop"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "profiler_stop");
        assert!(cmd.get("path").is_none());
    }

    #[test]
    fn test_profiler_invalid_subcommand() {
        let result = parse_command(&args("profiler foo"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::UnknownSubcommand { .. }
        ));
    }

    #[test]
    fn test_profiler_missing_subcommand() {
        let result = parse_command(&args("profiler"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    // === Eval Tests ===

    #[test]
    fn test_eval_basic() {
        let cmd = parse_command(&args("eval document.title"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "evaluate");
        assert_eq!(cmd["script"], "document.title");
    }

    #[test]
    fn test_eval_base64_short_flag() {
        // "document.title" in base64
        let cmd = parse_command(&args("eval -b ZG9jdW1lbnQudGl0bGU="), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "evaluate");
        assert_eq!(cmd["script"], "document.title");
    }

    #[test]
    fn test_eval_base64_long_flag() {
        // "document.title" in base64
        let cmd = parse_command(
            &args("eval --base64 ZG9jdW1lbnQudGl0bGU="),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "evaluate");
        assert_eq!(cmd["script"], "document.title");
    }

    #[test]
    fn test_eval_base64_with_special_chars() {
        // "document.querySelector('[src*=\"_next\"]')" in base64
        let cmd = parse_command(
            &args("eval -b ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignW3NyYyo9Il9uZXh0Il0nKQ=="),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "evaluate");
        assert_eq!(cmd["script"], "document.querySelector('[src*=\"_next\"]')");
    }

    #[test]
    fn test_eval_base64_invalid() {
        let result = parse_command(&args("eval -b !!!invalid!!!"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::InvalidValue { .. }));
        assert!(err.format().contains("Invalid base64"));
    }

    #[test]
    fn test_unknown_command() {
        let result = parse_command(&args("unknowncommand"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::UnknownCommand { .. }
        ));
    }

    #[test]
    fn test_empty_args() {
        let result = parse_command(&[], &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    // === Error message tests ===

    #[test]
    fn test_get_missing_subcommand() {
        let result = parse_command(&args("get"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::MissingArguments { .. }));
        assert!(err.format().contains("get"));
    }

    #[test]
    fn test_get_unknown_subcommand() {
        let result = parse_command(&args("get foo"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::UnknownSubcommand { .. }));
        assert!(err.format().contains("foo"));
        assert!(err.format().contains("text"));
    }

    #[test]
    fn test_get_text_missing_selector() {
        let result = parse_command(&args("get text"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::MissingArguments { .. }));
        assert!(err.format().contains("get text"));
    }

    // === Protocol alignment tests ===

    #[test]
    fn test_mouse_wheel() {
        let cmd = parse_command(&args("mouse wheel 100 50"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "wheel");
        assert_eq!(cmd["deltaY"], 100);
        assert_eq!(cmd["deltaX"], 50);
    }

    #[test]
    fn test_set_media() {
        let cmd = parse_command(&args("set media dark"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "emulatemedia");
        assert_eq!(cmd["colorScheme"], "dark");
        assert_eq!(cmd["reducedMotion"], "no-preference");
    }

    #[test]
    fn test_set_media_reduced_motion() {
        let cmd = parse_command(&args("set media light reduced-motion"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "emulatemedia");
        assert_eq!(cmd["colorScheme"], "light");
        assert_eq!(cmd["reducedMotion"], "reduce");
    }

    #[test]
    fn test_set_viewport() {
        let cmd = parse_command(&args("set viewport 1920 1080"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "viewport");
        assert_eq!(cmd["width"], 1920);
        assert_eq!(cmd["height"], 1080);
        assert!(cmd.get("deviceScaleFactor").is_none());
    }

    #[test]
    fn test_set_viewport_with_scale() {
        let cmd = parse_command(&args("set viewport 1920 1080 2"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "viewport");
        assert_eq!(cmd["width"], 1920);
        assert_eq!(cmd["height"], 1080);
        assert_eq!(cmd["deviceScaleFactor"], 2.0);
    }

    #[test]
    fn test_set_viewport_with_fractional_scale() {
        let cmd = parse_command(&args("set viewport 375 812 3"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "viewport");
        assert_eq!(cmd["width"], 375);
        assert_eq!(cmd["height"], 812);
        assert_eq!(cmd["deviceScaleFactor"], 3.0);
    }

    #[test]
    fn test_set_viewport_missing_height() {
        let result = parse_command(&args("set viewport 1920"), &default_flags());
        assert!(result.is_err());
    }

    #[test]
    fn test_set_viewport_invalid_scale() {
        let result = parse_command(&args("set viewport 1920 1080 abc"), &default_flags());
        assert!(result.is_err());
    }

    #[test]
    fn test_find_first_no_value() {
        let cmd = parse_command(&args("find first a click"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "nth");
        assert_eq!(cmd["index"], 0);
        assert!(cmd.get("value").is_none());
    }

    #[test]
    fn test_find_first_with_value() {
        let cmd = parse_command(&args("find first input fill hello"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "nth");
        assert_eq!(cmd["index"], 0);
        assert_eq!(cmd["value"], "hello");
    }

    #[test]
    fn test_find_nth_no_value() {
        let cmd = parse_command(&args("find nth 2 a click"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "nth");
        assert_eq!(cmd["index"], 2);
        assert!(cmd.get("value").is_none());
    }

    #[test]
    fn test_find_role_fill_does_not_include_flags_in_value() {
        let cmd = parse_command(
            &args("find role textbox fill hello --name username --exact"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "getbyrole");
        assert_eq!(cmd["role"], "textbox");
        assert_eq!(cmd["subaction"], "fill");
        assert_eq!(cmd["name"], "username");
        assert_eq!(cmd["exact"], true);
        assert_eq!(cmd["value"], "hello");
    }

    // === Download Tests ===

    #[test]
    fn test_download() {
        let cmd = parse_command(&args("download #btn ./file.pdf"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "download");
        assert_eq!(cmd["selector"], "#btn");
        assert_eq!(cmd["path"], "./file.pdf");
    }

    #[test]
    fn test_download_with_ref() {
        let cmd = parse_command(&args("download @e5 ./report.xlsx"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "download");
        assert_eq!(cmd["selector"], "@e5");
        assert_eq!(cmd["path"], "./report.xlsx");
    }

    #[test]
    fn test_download_missing_path() {
        let result = parse_command(&args("download #btn"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_download_missing_selector() {
        let result = parse_command(&args("download"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    // === Wait for Download Tests ===

    #[test]
    fn test_wait_download() {
        let cmd = parse_command(&args("wait --download"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitfordownload");
        assert!(cmd.get("path").is_none());
    }

    #[test]
    fn test_wait_download_with_path() {
        let cmd = parse_command(&args("wait --download ./file.pdf"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitfordownload");
        assert_eq!(cmd["path"], "./file.pdf");
    }

    #[test]
    fn test_wait_download_with_timeout() {
        let cmd =
            parse_command(&args("wait --download --timeout 30000"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitfordownload");
        assert_eq!(cmd["timeout"], 30000);
    }

    #[test]
    fn test_wait_download_with_path_and_timeout() {
        let cmd = parse_command(
            &args("wait --download ./file.pdf --timeout 30000"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "waitfordownload");
        assert_eq!(cmd["path"], "./file.pdf");
        assert_eq!(cmd["timeout"], 30000);
    }

    #[test]
    fn test_wait_download_short_flag() {
        let cmd = parse_command(&args("wait -d ./file.pdf"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "waitfordownload");
        assert_eq!(cmd["path"], "./file.pdf");
    }

    // === Default timeout (AGENT_BROWSER_DEFAULT_TIMEOUT) tests ===

    fn flags_with_default_timeout(ms: u64) -> Flags {
        let mut f = default_flags();
        f.default_timeout = Some(ms);
        f
    }

    #[test]
    fn test_wait_selector_inherits_default_timeout() {
        let flags = flags_with_default_timeout(3000);
        let cmd = parse_command(&args("wait #element"), &flags).unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["selector"], "#element");
        assert_eq!(cmd["timeout"], 3000);
    }

    #[test]
    fn test_wait_url_inherits_default_timeout() {
        let flags = flags_with_default_timeout(4000);
        let cmd = parse_command(&args("wait --url **/dashboard"), &flags).unwrap();
        assert_eq!(cmd["action"], "waitforurl");
        assert_eq!(cmd["timeout"], 4000);
    }

    #[test]
    fn test_wait_load_inherits_default_timeout() {
        let flags = flags_with_default_timeout(4000);
        let cmd = parse_command(&args("wait --load networkidle"), &flags).unwrap();
        assert_eq!(cmd["action"], "waitforloadstate");
        assert_eq!(cmd["timeout"], 4000);
    }

    #[test]
    fn test_wait_fn_inherits_default_timeout() {
        let flags = flags_with_default_timeout(4000);
        let cmd = parse_command(&args("wait --fn window.ready"), &flags).unwrap();
        assert_eq!(cmd["action"], "waitforfunction");
        assert_eq!(cmd["timeout"], 4000);
    }

    #[test]
    fn test_wait_text_inherits_default_timeout() {
        let flags = flags_with_default_timeout(2000);
        let cmd = parse_command(&args("wait --text Welcome"), &flags).unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["text"], "Welcome");
        assert_eq!(cmd["timeout"], 2000);
    }

    #[test]
    fn test_wait_download_inherits_default_timeout() {
        let flags = flags_with_default_timeout(5000);
        let cmd = parse_command(&args("wait --download"), &flags).unwrap();
        assert_eq!(cmd["action"], "waitfordownload");
        assert_eq!(cmd["timeout"], 5000);
    }

    #[test]
    fn test_wait_explicit_timeout_overrides_default() {
        let flags = flags_with_default_timeout(5000);
        let cmd = parse_command(&args("wait --text Welcome --timeout 1000"), &flags).unwrap();
        assert_eq!(cmd["timeout"], 1000);
    }

    #[test]
    fn test_wait_no_default_timeout_omits_field() {
        let cmd = parse_command(&args("wait #element"), &default_flags()).unwrap();
        assert!(cmd.get("timeout").is_none());
    }

    // === Connect (CDP) tests ===

    #[test]
    fn test_connect_with_port() {
        let cmd = parse_command(&args("connect 9222"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["cdpPort"], 9222);
        assert!(cmd.get("cdpUrl").is_none());
    }

    #[test]
    fn test_connect_with_ws_url() {
        let input: Vec<String> = vec![
            "connect".to_string(),
            "ws://localhost:9222/devtools/browser/abc123".to_string(),
        ];
        let cmd = parse_command(&input, &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["cdpUrl"], "ws://localhost:9222/devtools/browser/abc123");
        assert!(cmd.get("cdpPort").is_none());
    }

    #[test]
    fn test_connect_with_wss_url() {
        let input: Vec<String> = vec![
            "connect".to_string(),
            "wss://remote-browser.example.com/cdp?token=xyz".to_string(),
        ];
        let cmd = parse_command(&input, &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(
            cmd["cdpUrl"],
            "wss://remote-browser.example.com/cdp?token=xyz"
        );
        assert!(cmd.get("cdpPort").is_none());
    }

    #[test]
    fn test_connect_with_http_url() {
        let input: Vec<String> = vec!["connect".to_string(), "http://localhost:9222".to_string()];
        let cmd = parse_command(&input, &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["cdpUrl"], "http://localhost:9222");
        assert!(cmd.get("cdpPort").is_none());
    }

    #[test]
    fn test_connect_missing_argument() {
        let result = parse_command(&args("connect"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_connect_invalid_port() {
        let result = parse_command(&args("connect notanumber"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::InvalidValue { .. }));
        assert!(err.format().contains("not a valid port number or URL"));
    }

    #[test]
    fn test_connect_port_zero() {
        let result = parse_command(&args("connect 0"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::InvalidValue { .. }));
        assert!(err.format().contains("port must be greater than 0"));
    }

    #[test]
    fn test_connect_port_out_of_range() {
        let result = parse_command(&args("connect 65536"), &default_flags());
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(matches!(err, ParseError::InvalidValue { .. }));
        assert!(err.format().contains("out of range"));
        assert!(err.format().contains("1-65535"));
    }

    #[test]
    fn test_connect_port_max_valid() {
        let cmd = parse_command(&args("connect 65535"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["cdpPort"], 65535);
    }

    #[test]
    fn test_connect_port_min_valid() {
        let cmd = parse_command(&args("connect 1"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "launch");
        assert_eq!(cmd["cdpPort"], 1);
    }

    // === Runtime stream control tests ===

    #[test]
    fn test_stream_enable_auto_port() {
        let cmd = parse_command(&args("stream enable"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "stream_enable");
        assert!(cmd.get("port").is_none());
    }

    #[test]
    fn test_stream_enable_with_port() {
        let cmd = parse_command(&args("stream enable --port 9223"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "stream_enable");
        assert_eq!(cmd["port"], 9223);
    }

    #[test]
    fn test_stream_status() {
        let cmd = parse_command(&args("stream status"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "stream_status");
    }

    #[test]
    fn test_stream_disable() {
        let cmd = parse_command(&args("stream disable"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "stream_disable");
    }

    #[test]
    fn test_stream_enable_invalid_port() {
        let result = parse_command(&args("stream enable --port abc"), &default_flags());
        assert!(matches!(result, Err(ParseError::InvalidValue { .. })));
    }

    #[test]
    fn test_stream_missing_subcommand() {
        let result = parse_command(&args("stream"), &default_flags());
        assert!(matches!(result, Err(ParseError::MissingArguments { .. })));
    }

    // === Trace Tests ===

    #[test]
    fn test_trace_start() {
        let cmd = parse_command(&args("trace start"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "trace_start");
    }

    #[test]
    fn test_trace_stop_with_path() {
        let cmd = parse_command(&args("trace stop ./trace.zip"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "trace_stop");
        assert_eq!(cmd["path"], "./trace.zip");
    }

    #[test]
    fn test_trace_stop_without_path() {
        let cmd = parse_command(&args("trace stop"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "trace_stop");
        assert!(cmd.get("path").is_none() || cmd["path"].is_null());
    }

    // === Diff Tests ===

    #[test]
    fn test_diff_snapshot_basic() {
        let cmd = parse_command(&args("diff snapshot"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "diff_snapshot");
    }

    #[test]
    fn test_diff_snapshot_baseline() {
        let cmd = parse_command(
            &args("diff snapshot --baseline before.txt"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_snapshot");
        assert_eq!(cmd["baseline"], "before.txt");
    }

    #[test]
    fn test_diff_snapshot_selector_compact_depth() {
        let cmd = parse_command(
            &args("diff snapshot --selector #main --compact --depth 3"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_snapshot");
        assert_eq!(cmd["selector"], "#main");
        assert_eq!(cmd["compact"], true);
        assert_eq!(cmd["maxDepth"], 3);
    }

    #[test]
    fn test_diff_snapshot_short_flags() {
        let cmd = parse_command(
            &args("diff snapshot -b snap.txt -s .content -c -d 2"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_snapshot");
        assert_eq!(cmd["baseline"], "snap.txt");
        assert_eq!(cmd["selector"], ".content");
        assert_eq!(cmd["compact"], true);
        assert_eq!(cmd["maxDepth"], 2);
    }

    #[test]
    fn test_diff_screenshot_baseline() {
        let cmd = parse_command(
            &args("diff screenshot --baseline before.png"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_screenshot");
        assert_eq!(cmd["baseline"], "before.png");
    }

    #[test]
    fn test_diff_screenshot_all_options() {
        let cmd = parse_command(
            &args("diff screenshot --baseline b.png --output d.png --threshold 0.2 --selector #hero --full"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_screenshot");
        assert_eq!(cmd["baseline"], "b.png");
        assert_eq!(cmd["output"], "d.png");
        assert_eq!(cmd["threshold"], 0.2);
        assert_eq!(cmd["selector"], "#hero");
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_diff_screenshot_missing_baseline() {
        let result = parse_command(&args("diff screenshot"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_command_full_flag() {
        let cmd = parse_command(
            &args("diff screenshot --baseline b.png --full"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_screenshot");
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_diff_screenshot_command_full_flag_shorthand() {
        let cmd = parse_command(
            &args("diff screenshot --baseline b.png -f"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_screenshot");
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_diff_url_basic() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_url");
        assert_eq!(cmd["url1"], "https://a.com");
        assert_eq!(cmd["url2"], "https://b.com");
    }

    #[test]
    fn test_diff_url_with_screenshot_full() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com --screenshot --full"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_url");
        assert_eq!(cmd["screenshot"], true);
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_diff_url_with_wait_until() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com --wait-until networkidle"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_url");
        assert_eq!(cmd["waitUntil"], "networkidle");
    }

    #[test]
    fn test_diff_url_command_full_flag() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com --full"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["fullPage"], true);
    }

    #[test]
    fn test_diff_missing_subcommand() {
        let result = parse_command(&args("diff"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_unknown_subcommand() {
        let result = parse_command(&args("diff invalid"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::UnknownSubcommand { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_baseline_missing_value() {
        let result = parse_command(&args("diff snapshot --baseline"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_selector_missing_value() {
        let result = parse_command(&args("diff snapshot --selector"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_depth_missing_value() {
        let result = parse_command(&args("diff snapshot --depth"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_threshold_missing_value() {
        let result = parse_command(
            &args("diff screenshot --baseline b.png --threshold"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_output_missing_value() {
        let result = parse_command(
            &args("diff screenshot --baseline b.png --output"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_url_wait_until_missing_value() {
        let result = parse_command(
            &args("diff url https://a.com https://b.com --wait-until"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_unexpected_arg() {
        let result = parse_command(&args("diff snapshot foo"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_unexpected_arg() {
        let result = parse_command(
            &args("diff screenshot --baseline b.png unexpected"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_url_unexpected_arg() {
        let result = parse_command(
            &args("diff url https://a.com https://b.com extra"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_unknown_flag() {
        let result = parse_command(&args("diff snapshot --invalid"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_url_missing_urls() {
        let result = parse_command(&args("diff url"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_url_missing_second_url() {
        let result = parse_command(&args("diff url https://a.com"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_depth_invalid_value() {
        let result = parse_command(&args("diff snapshot --depth abc"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_threshold_invalid_value() {
        let result = parse_command(
            &args("diff screenshot --baseline b.png --threshold abc"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_threshold_out_of_range() {
        let result = parse_command(
            &args("diff screenshot --baseline b.png --threshold 1.5"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_screenshot_threshold_negative() {
        let result = parse_command(
            &args("diff screenshot --baseline b.png --threshold -0.5"),
            &default_flags(),
        );
        assert!(result.is_err());
    }

    #[test]
    fn test_diff_url_with_selector() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com --selector #main"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_url");
        assert_eq!(cmd["selector"], "#main");
    }

    #[test]
    fn test_diff_url_with_compact_depth() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com --compact --depth 3"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_url");
        assert_eq!(cmd["compact"], true);
        assert_eq!(cmd["maxDepth"], 3);
    }

    #[test]
    fn test_diff_url_with_short_snapshot_flags() {
        let cmd = parse_command(
            &args("diff url https://a.com https://b.com -s .content -c -d 2"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "diff_url");
        assert_eq!(cmd["selector"], ".content");
        assert_eq!(cmd["compact"], true);
        assert_eq!(cmd["maxDepth"], 2);
    }

    #[test]
    fn test_diff_url_depth_invalid_value() {
        let result = parse_command(
            &args("diff url https://a.com https://b.com --depth abc"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_snapshot_depth_negative_value() {
        let result = parse_command(&args("diff snapshot --depth -1"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_url_depth_negative_value() {
        let result = parse_command(
            &args("diff url https://a.com https://b.com --depth -1"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::InvalidValue { .. }
        ));
    }

    #[test]
    fn test_diff_url_selector_missing_value() {
        let result = parse_command(
            &args("diff url https://a.com https://b.com --selector"),
            &default_flags(),
        );
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    // === Scroll Tests ===

    #[test]
    fn test_scroll_defaults() {
        let cmd = parse_command(&args("scroll"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "scroll");
        assert_eq!(cmd["direction"], "down");
        assert_eq!(cmd["amount"], 300);
        assert!(cmd.get("selector").is_none());
    }

    #[test]
    fn test_scroll_direction_and_amount() {
        let cmd = parse_command(&args("scroll up 200"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "scroll");
        assert_eq!(cmd["direction"], "up");
        assert_eq!(cmd["amount"], 200);
    }

    #[test]
    fn test_scroll_with_selector() {
        let cmd = parse_command(
            &args("scroll down 500 --selector div.scroll-container"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "scroll");
        assert_eq!(cmd["direction"], "down");
        assert_eq!(cmd["amount"], 500);
        assert_eq!(cmd["selector"], "div.scroll-container");
    }

    #[test]
    fn test_scroll_with_selector_short_flag() {
        let cmd = parse_command(&args("scroll left 100 -s .sidebar"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "scroll");
        assert_eq!(cmd["direction"], "left");
        assert_eq!(cmd["amount"], 100);
        assert_eq!(cmd["selector"], ".sidebar");
    }

    #[test]
    fn test_scroll_selector_before_positional() {
        let cmd =
            parse_command(&args("scroll --selector .panel down 400"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "scroll");
        assert_eq!(cmd["direction"], "down");
        assert_eq!(cmd["amount"], 400);
        assert_eq!(cmd["selector"], ".panel");
    }

    #[test]
    fn test_scroll_selector_only() {
        let cmd = parse_command(&args("scroll --selector .content"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "scroll");
        assert_eq!(cmd["direction"], "down");
        assert_eq!(cmd["amount"], 300);
        assert_eq!(cmd["selector"], ".content");
    }

    #[test]
    fn test_scroll_selector_missing_value() {
        let result = parse_command(&args("scroll down 500 --selector"), &default_flags());
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            ParseError::MissingArguments { .. }
        ));
    }

    // === Inspect / CDP URL ===

    #[test]
    fn test_inspect() {
        let cmd = parse_command(&args("inspect"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "inspect");
    }

    #[test]
    fn test_get_cdp_url() {
        let cmd = parse_command(&args("get cdp-url"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "cdp_url");
    }

    // === Batch Tests ===

    #[test]
    fn test_batch_default() {
        let cmd = parse_command(&args("batch"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "batch");
        assert_eq!(cmd["bail"], false);
    }

    #[test]
    fn test_batch_with_bail() {
        let cmd = parse_command(&args("batch --bail"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "batch");
        assert_eq!(cmd["bail"], true);
    }

    #[test]
    fn test_batch_with_args() {
        let cmd_args = vec![
            "batch".to_string(),
            "open https://example.com".to_string(),
            "screenshot".to_string(),
        ];
        let cmd = parse_command(&cmd_args, &default_flags()).unwrap();
        assert_eq!(cmd["action"], "batch");
        assert_eq!(cmd["bail"], false);
        let commands = cmd["commands"].as_array().unwrap();
        assert_eq!(commands.len(), 2);
        assert_eq!(commands[0], "open https://example.com");
        assert_eq!(commands[1], "screenshot");
    }

    #[test]
    fn test_batch_with_args_and_bail() {
        let cmd_args = vec![
            "batch".to_string(),
            "--bail".to_string(),
            "open https://example.com".to_string(),
            "screenshot".to_string(),
        ];
        let cmd = parse_command(&cmd_args, &default_flags()).unwrap();
        assert_eq!(cmd["action"], "batch");
        assert_eq!(cmd["bail"], true);
        let commands = cmd["commands"].as_array().unwrap();
        assert_eq!(commands.len(), 2);
    }

    #[test]
    fn test_batch_no_args_no_commands_field() {
        let cmd = parse_command(&args("batch"), &default_flags()).unwrap();
        assert!(cmd.get("commands").is_none());
    }

    // === parse_find: friendly error when action verb is missing ===

    #[test]
    fn test_find_role_missing_action_verb_with_name_flag() {
        let err =
            parse_command(&args("find role button --name Submit"), &default_flags()).unwrap_err();
        let msg = err.format();
        assert!(
            msg.contains("Missing action verb"),
            "expected 'Missing action verb' in error, got: {msg}"
        );
        assert!(
            msg.contains("Did you mean"),
            "expected 'Did you mean' suggestion, got: {msg}"
        );
        assert!(
            msg.contains("--name"),
            "error should echo the offending flag back, got: {msg}"
        );
    }

    #[test]
    fn test_find_testid_missing_action_verb_with_exact_flag() {
        let err = parse_command(&args("find testid foo --exact"), &default_flags()).unwrap_err();
        assert!(err.format().contains("Missing action verb"));
    }

    #[test]
    fn test_find_role_with_action_still_works() {
        let cmd = parse_command(
            &args("find role button click --name Submit"),
            &default_flags(),
        )
        .unwrap();
        assert_eq!(cmd["action"], "getbyrole");
        assert_eq!(cmd["subaction"], "click");
        assert_eq!(cmd["name"], "Submit");
    }

    #[test]
    fn test_find_role_default_subaction_click_when_no_action() {
        // Backwards compat: `find role button` (no flags, no action) keeps
        // defaulting to click — only `--xxx` in action position errors.
        let cmd = parse_command(&args("find role button"), &default_flags()).unwrap();
        assert_eq!(cmd["subaction"], "click");
    }

    // === wait --gone / --hidden ===

    #[test]
    fn test_wait_selector_default_visible() {
        let cmd = parse_command(&args("wait .toast"), &default_flags()).unwrap();
        assert_eq!(cmd["action"], "wait");
        assert_eq!(cmd["selector"], ".toast");
        assert!(cmd.get("state").is_none(), "default state stays implicit");
    }

    #[test]
    fn test_wait_selector_gone_sets_detached_state() {
        let cmd = parse_command(&args("wait .toast --gone"), &default_flags()).unwrap();
        assert_eq!(cmd["selector"], ".toast");
        assert_eq!(cmd["state"], "detached");
    }

    #[test]
    fn test_wait_selector_hidden_sets_hidden_state() {
        let cmd = parse_command(&args("wait .toast --hidden"), &default_flags()).unwrap();
        assert_eq!(cmd["state"], "hidden");
    }

    #[test]
    fn test_wait_gone_with_timeout() {
        let cmd =
            parse_command(&args("wait .modal --gone --timeout 2000"), &default_flags()).unwrap();
        assert_eq!(cmd["selector"], ".modal");
        assert_eq!(cmd["state"], "detached");
        assert_eq!(cmd["timeout"], 2000);
    }

    #[test]
    fn test_wait_numeric_timeout_still_works() {
        // `wait 500` keeps meaning "sleep 500ms", not "wait for selector 500"
        let cmd = parse_command(&args("wait 500"), &default_flags()).unwrap();
        assert_eq!(cmd["timeout"], 500);
        assert!(cmd.get("selector").is_none());
    }
}
