use homeboy::cli_surface::{Cli, Commands};
use homeboy::core::lab_routing::{
    self, LabDispatchObserver, LabRouteOutcome, LabRoutingRequest, NoopLabDispatchObserver,
};
use homeboy::core::runners::{self, RunnerExecOptions};
use std::collections::HashMap;

use crate::commands::utils::output::write_output_file;

pub fn route_after_parse(
    cli: &Cli,
    normalized_args: &[String],
    output_file: Option<&str>,
) -> homeboy::core::Result<Option<i32>> {
    if lab_routing::is_lab_offload_subprocess() {
        return Ok(None);
    }

    if let (Some(runner_id), Commands::Runs(args)) = (cli.runner.as_deref(), &cli.command) {
        if !is_runs_list_runner_option(normalized_args) {
            return Err(crate::commands::runs::global_runner_error(args, runner_id));
        }

        return Ok(None);
    }

    if is_lab_command_local_runner_option(&cli.command) {
        return Ok(None);
    }

    if let (Some(runner_id), Commands::Rig(args)) = (cli.runner.as_deref(), &cli.command) {
        if args.is_runner_source_management_command() {
            let (stdout, stderr, exit_code) =
                run_rig_source_management_on_runner(runner_id, normalized_args, output_file)?;
            if !stderr.is_empty() {
                eprint!("{stderr}");
            }
            print!("{stdout}");
            return Ok(Some(exit_code));
        }
    }

    let lab_command = lab_offload_command(&cli.command)?;

    let trace_runner_id = if matches!(cli.command, Commands::Trace(_)) {
        cli.runner
            .clone()
            .or_else(|| runners::resolve_default_lab_runner().ok().flatten())
    } else {
        None
    };

    let observer = lab_dispatch_observer(cli, normalized_args, trace_runner_id.as_deref());
    let active_run_id = observer.run_id().map(str::to_string);

    let mutation_flag = cli.command.lab_offload_mutation_flag();

    // For component-targeted write/fix commands (`homeboy lint --fix <component>`,
    // `homeboy refactor --from lint --write <component>`), the component is
    // resolved on the controller to its source checkout and the args are
    // rewritten to `--path <source>`. Without this, the offload syncs and
    // diff-captures the controller's working directory while the remote re-resolves
    // the positional component to the runner's registered checkout and writes
    // fixes there — so the source-tree mutation lands outside the captured
    // workspace and the runner returns no patch to apply (#4315).
    let rewritten_args = (mutation_flag.is_some())
        .then(|| rewrite_component_target_to_path(&cli.command, normalized_args))
        .flatten();
    let routed_args = rewritten_args.as_deref().unwrap_or(normalized_args);

    let outcome = lab_routing::dispatch_lab_offload(
        LabRoutingRequest {
            command: lab_command,
            normalized_args: routed_args,
            explicit_runner: cli.runner.as_deref(),
            force_hot: cli.force_hot,
            local_policy: runners::LabLocalExecutionPolicy {
                allow_local_hot: cli.allow_local_hot,
                allow_local_fallback: cli.allow_local_fallback,
            },
            allow_dirty_lab_workspace: cli.allow_dirty_lab_workspace,
            capture_patch: mutation_flag.is_some(),
            mutation_flag,
            timeout: None,
            active_run_id: active_run_id.as_deref(),
        },
        trace_runner_id.as_deref(),
        observer,
    )?;

    match outcome {
        LabRouteOutcome::RunLocal => Ok(None),
        LabRouteOutcome::Offloaded(output) => {
            if !output.stderr.is_empty() {
                eprint!("{}", output.stderr);
            }
            if let Some(path) = output_file {
                write_offloaded_stdout(path, &output.stdout)?;
            }
            print!("{}", output.stdout);
            Ok(Some(output.exit_code))
        }
    }
}

/// Build the Lab dispatch observer for the parsed command. Only `trace`
/// participates in dispatch observation; every other command uses the no-op
/// observer. The core routing service owns the observation lifecycle; this
/// adapter only supplies the implementation.
fn lab_dispatch_observer(
    cli: &Cli,
    normalized_args: &[String],
    trace_runner_id: Option<&str>,
) -> Box<dyn LabDispatchObserver> {
    match &cli.command {
        Commands::Trace(args) => crate::commands::trace::start_lab_dispatch_observation(
            args,
            normalized_args,
            trace_runner_id,
        )
        .map(|observation| Box::new(observation) as Box<dyn LabDispatchObserver>)
        .unwrap_or_else(|| Box::new(NoopLabDispatchObserver)),
        _ => Box::new(NoopLabDispatchObserver),
    }
}

fn run_rig_source_management_on_runner(
    runner_id: &str,
    normalized_args: &[String],
    output_file: Option<&str>,
) -> homeboy::core::Result<(String, String, i32)> {
    let runner = runners::load(runner_id)?;
    let homeboy_path = runner.settings.homeboy_path.as_deref().unwrap_or("homeboy");
    let command = runner_rig_source_management_command(homeboy_path, normalized_args);

    // Remote-execution preflight before dispatching caller-derived argv to the
    // runner (#5093):
    // 1. Path-translation: reject any forwarded argument that still embeds the
    //    controller-local working directory instead of the runner-resident
    //    workspace, so a controller-only path never reaches the remote runtime.
    // 2. Capability parity: validate the runner can run the forwarded `homeboy`
    //    binary before execution starts (enforced by `runners::exec` against the
    //    supplied `RunnerCapabilityPreflight`).
    let remote_cwd = runner.workspace_root.clone().unwrap_or_default();
    if let Ok(local_cwd) = std::env::current_dir() {
        runners::preflight_remote_argv_path_translation(
            "Rig source management",
            runner_id,
            &command,
            &local_cwd,
            &remote_cwd,
        )?;
    }
    let required_commands: Vec<String> = command
        .first()
        .filter(|program| !program.trim().is_empty())
        .cloned()
        .into_iter()
        .collect();
    let capability_preflight =
        (!required_commands.is_empty()).then(|| runners::RunnerCapabilityPreflight {
            command: "rig.source-management".to_string(),
            required_commands,
            ..Default::default()
        });

    let (output, exit_code) = runners::exec(
        runner_id,
        RunnerExecOptions {
            cwd: runner.workspace_root.clone(),
            project_id: None,
            allow_diagnostic_ssh: false,
            command,
            env: HashMap::new(),
            secret_env_names: Vec::new(),
            capture_patch: false,
            raw_exec: false,
            source_snapshot: None,
            capability_preflight,
            required_extensions: Vec::new(),
            require_paths: Vec::new(),
        },
    )?;

    if let Some(path) = output_file {
        write_offloaded_stdout(path, &output.stdout)?;
    }

    Ok((output.stdout, output.stderr, exit_code))
}

fn runner_rig_source_management_command(
    homeboy_path: &str,
    normalized_args: &[String],
) -> Vec<String> {
    let mut command = vec![homeboy_path.to_string()];
    let mut iter = normalized_args.iter().skip(1).peekable();
    while let Some(arg) = iter.next() {
        if arg == "--runner" || arg == "--output" || arg == "--artifact-root" {
            iter.next();
            continue;
        }
        if arg == "--allow-local-fallback"
            || arg == "--allow-dirty-lab-workspace"
            || arg == "--allow-local-hot"
        {
            continue;
        }
        if arg.starts_with("--runner=")
            || arg.starts_with("--output=")
            || arg.starts_with("--artifact-root=")
        {
            continue;
        }
        command.push(arg.clone());
    }
    command
}

fn is_runs_list_runner_option(args: &[String]) -> bool {
    let Some(runs_index) = args.iter().position(|arg| arg == "runs") else {
        return false;
    };
    let Some(list_index) = args.iter().position(|arg| arg == "list") else {
        return false;
    };

    list_index > runs_index
        && args.iter().enumerate().any(|(index, arg)| {
            index > list_index && (arg == "--runner" || arg.starts_with("--runner="))
        })
}

fn is_lab_command_local_runner_option(command: &Commands) -> bool {
    matches!(command, Commands::Lab(_))
}

fn write_offloaded_stdout(path: &str, stdout: &str) -> homeboy::core::Result<()> {
    write_output_file(path, stdout)
}

fn lab_offload_command(
    command: &Commands,
) -> homeboy::core::Result<Option<runners::LabOffloadCommand>> {
    let Some(contract) = command.lab_contract() else {
        return Ok(None);
    };
    let required_extensions = command.lab_required_extensions()?;
    Ok(Some(lab_routing::lab_offload_command_from_contract(
        contract,
        required_extensions,
    )))
}

/// When a `lint --fix` / `refactor --write` command targets a component by id
/// (positionally or via `--component`/`--components`), resolve that component to
/// its on-disk source path and rewrite the offload args to `--path <source>`.
///
/// The Lab offload patch-capture pipeline (`lab_offload_source_path` →
/// workspace sync → before/after diff) keys entirely off the resolved source
/// path. A bare positional component id resolves to the controller working
/// directory for the sync/diff, but on the remote runner it re-resolves to the
/// runner's registered component checkout — so write fixes land outside the
/// captured workspace and no patch is produced (#4315). Rewriting to `--path`
/// makes the synced workspace, the remote command's working tree, and the
/// captured diff all reference the same directory.
///
/// Returns `None` (leaving args untouched) when there is nothing to rewrite:
/// no component target, an explicit `--path` is already present, the component
/// cannot be resolved, or the command is not a component-targeted lint/refactor.
fn rewrite_component_target_to_path(
    command: &Commands,
    normalized_args: &[String],
) -> Option<Vec<String>> {
    let (component_id, has_path_override) = match command {
        Commands::Lint(args) => (
            args.lab_offload_positional_component(),
            args.lab_offload_has_path_override(),
        ),
        Commands::Refactor(args) if args.is_hot_resource_command() => (
            args.lab_offload_positional_component(),
            args.lab_offload_has_path_override(),
        ),
        _ => return None,
    };

    if has_path_override {
        return None;
    }
    let component_id = component_id?;

    let source_path = resolve_component_source_path(&component_id)?;
    Some(strip_component_target_args(
        normalized_args,
        &component_id,
        &source_path,
    ))
}

/// Resolve a component id to its canonical on-disk source path. Returns `None`
/// when resolution fails so the caller can fall back to the original args and
/// let the normal offload path surface any downstream error.
fn resolve_component_source_path(component_id: &str) -> Option<String> {
    use homeboy::core::engine::execution_context::{self, ResolveOptions};

    let ctx = execution_context::resolve(&ResolveOptions {
        component_id: Some(component_id.to_string()),
        ..ResolveOptions::default()
    })
    .ok()?;
    Some(ctx.source_path.to_string_lossy().to_string())
}

/// Drop component-targeting args (the bare positional id and any
/// `-c`/`--component`/`--components` flags) and append `--path <source_path>`.
fn strip_component_target_args(
    normalized_args: &[String],
    component_id: &str,
    source_path: &str,
) -> Vec<String> {
    let mut rewritten = Vec::with_capacity(normalized_args.len() + 1);
    let mut iter = normalized_args.iter().peekable();
    let mut passthrough = false;
    let mut positional_stripped = false;
    while let Some(arg) = iter.next() {
        if passthrough {
            rewritten.push(arg.clone());
            continue;
        }
        if arg == "--" {
            passthrough = true;
            rewritten.push(arg.clone());
            continue;
        }
        // Flagged component selectors that consume a following value.
        if arg == "-c" || arg == "--component" || arg == "--components" {
            let _ = iter.next();
            continue;
        }
        // Inline `--component=<id>` / `--components=<list>` / `-c<id>` forms.
        if arg.starts_with("--component=")
            || arg.starts_with("--components=")
            || (arg.starts_with("-c") && arg.len() > 2 && !arg.starts_with("--"))
        {
            continue;
        }
        // The bare positional component token (strip only the first match so an
        // unrelated later argument that happens to equal the id is preserved).
        if !positional_stripped && !arg.starts_with('-') && arg == component_id {
            positional_stripped = true;
            continue;
        }
        rewritten.push(arg.clone());
    }
    rewritten.push("--path".to_string());
    rewritten.push(source_path.to_string());
    rewritten
}

#[cfg(test)]
mod tests {
    use super::*;
    use clap::Parser;
    use std::sync::{Mutex, MutexGuard, OnceLock};
    use tempfile::tempdir;

    struct EnvGuard {
        name: &'static str,
        previous: Option<String>,
        _guard: MutexGuard<'static, ()>,
    }

    impl EnvGuard {
        fn set(name: &'static str, value: &str) -> Self {
            let guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
            let previous = std::env::var(name).ok();
            std::env::set_var(name, value);
            Self {
                name,
                previous,
                _guard: guard,
            }
        }

        fn remove(name: &'static str) -> Self {
            let guard = env_lock().lock().unwrap_or_else(|err| err.into_inner());
            let previous = std::env::var(name).ok();
            std::env::remove_var(name);
            Self {
                name,
                previous,
                _guard: guard,
            }
        }
    }

    fn env_lock() -> &'static Mutex<()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
    }

    impl Drop for EnvGuard {
        fn drop(&mut self) {
            match &self.previous {
                Some(value) => std::env::set_var(self.name, value),
                None => std::env::remove_var(self.name),
            }
        }
    }

    #[test]
    fn non_lab_command_continues_local_dispatch() {
        let cli = Cli::parse_from(["homeboy", "status"]);

        let outcome = route_after_parse(&cli, &["homeboy".into(), "status".into()], None).unwrap();

        assert_eq!(outcome, None);
    }

    #[test]
    fn hot_local_only_command_records_lab_plan_metadata() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let cli = Cli::parse_from(["homeboy", "lint", "--changed-since", "origin/main"]);

        let outcome = route_after_parse(
            &cli,
            &[
                "homeboy".into(),
                "lint".into(),
                "--changed-since".into(),
                "origin/main".into(),
            ],
            None,
        )
        .unwrap();

        assert_eq!(outcome, None);
        let raw = std::env::var(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV)
            .expect("Lab routing metadata captured");
        let metadata: serde_json::Value = serde_json::from_str(&raw).unwrap();
        assert_eq!(metadata["status"], "skipped");
        assert_eq!(metadata["source"], "automatic");
        assert!(metadata["plan_id"]
            .as_str()
            .unwrap()
            .contains("lab_offload"));
        assert!(metadata["fallback_reason"]
            .as_str()
            .unwrap()
            .contains("Changed-scope lint runs stay local"));
    }

    #[test]
    fn explicit_runner_for_local_only_hot_command_errors_from_lab_plan_path() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "homeboy-lab",
            "test",
            "--changed-since",
            "origin/main",
        ]);

        let err = route_after_parse(
            &cli,
            &[
                "homeboy".into(),
                "--runner".into(),
                "homeboy-lab".into(),
                "test".into(),
                "--changed-since".into(),
                "origin/main".into(),
            ],
            None,
        )
        .expect_err("local-only hot command rejects explicit runner");

        assert_eq!(err.code.as_str(), "validation.invalid_argument");
        assert!(err.message.contains("--runner is unavailable"));
        assert!(err.message.contains("test --changed-since"));
    }

    #[test]
    fn lab_offload_subprocess_skips_recursive_lab_routing() {
        let _env = EnvGuard::set(
            homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV,
            r#"{"status":"offloaded"}"#,
        );
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "homeboy-lab",
            "trace",
            "--rig",
            "gutenberg-pattern-preview-assets",
            "gutenberg",
            "pattern-preview-assets",
        ]);
        let normalized = [
            "homeboy".to_string(),
            "--runner".to_string(),
            "homeboy-lab".to_string(),
            "trace".to_string(),
            "--rig".to_string(),
            "gutenberg-pattern-preview-assets".to_string(),
            "gutenberg".to_string(),
            "pattern-preview-assets".to_string(),
        ];

        let outcome = route_after_parse(&cli, &normalized, None).unwrap();

        assert_eq!(outcome, None);
    }

    #[test]
    fn lab_extension_sync_runner_option_routes_to_lab_command_handler() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let normalized = vec![
            "homeboy".to_string(),
            "lab".to_string(),
            "extension-sync".to_string(),
            "--runner".to_string(),
            "homeboy-lab".to_string(),
            "--source".to_string(),
            "/tmp/wordpress-extension".to_string(),
            "--id".to_string(),
            "wordpress".to_string(),
            "--ref".to_string(),
            "main".to_string(),
        ];
        let cli = Cli::parse_from(&normalized);

        let outcome = route_after_parse(&cli, &normalized, None)
            .expect("lab extension-sync owns its runner option locally");

        assert_eq!(outcome, None);
    }

    #[test]
    fn lab_extension_sync_runner_with_force_hot_routes_locally() {
        // Regression for #4343: `lab extension-sync --runner <id> --force-hot`
        // must NOT be rejected by the global runner validation. The command owns
        // runner selection locally and routes through its own handler.
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let normalized = vec![
            "homeboy".to_string(),
            "lab".to_string(),
            "extension-sync".to_string(),
            "--runner".to_string(),
            "homeboy-lab".to_string(),
            "--source".to_string(),
            "https://github.com/Extra-Chill/homeboy-extensions.git".to_string(),
            "--id".to_string(),
            "wordpress".to_string(),
            "--ref".to_string(),
            "44426b5874df018a7bdb9742098f936f53b1c0ff".to_string(),
            "--force-hot".to_string(),
        ];
        let cli = Cli::parse_from(&normalized);

        // The global `--runner` flag is populated (global = true), but the lab
        // command short-circuits to local routing instead of the offload path
        // that would reject a runner on a non-portable command.
        assert_eq!(cli.runner.as_deref(), Some("homeboy-lab"));

        let outcome = route_after_parse(&cli, &normalized, None)
            .expect("lab extension-sync --runner --force-hot must route locally, not reject");

        assert_eq!(outcome, None);
        assert!(std::env::var(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV).is_err());
    }

    #[test]
    fn trace_lab_dispatch_timeout_reads_env_override() {
        let _env = EnvGuard::set(lab_routing::LAB_TRACE_DISPATCH_TIMEOUT_ENV, "7");

        assert_eq!(
            lab_routing::lab_trace_dispatch_timeout(),
            std::time::Duration::from_secs(7)
        );
    }

    #[test]
    fn offloaded_stdout_write_preserves_bytes_for_output_file() {
        let dir = tempdir().unwrap();
        let output_path = dir.path().join("out.json");

        write_offloaded_stdout(&output_path.to_string_lossy(), "{\"ok\":true}\n").unwrap();

        assert_eq!(
            std::fs::read_to_string(output_path).unwrap(),
            "{\"ok\":true}\n"
        );
    }

    #[test]
    fn runner_rig_source_management_command_strips_controller_globals() {
        let normalized = vec![
            "homeboy".to_string(),
            "rig".to_string(),
            "sources".to_string(),
            "list".to_string(),
            "--runner".to_string(),
            "homeboy-lab".to_string(),
            "--output=./sources.json".to_string(),
            "--allow-local-fallback".to_string(),
        ];

        assert_eq!(
            runner_rig_source_management_command("/usr/local/bin/homeboy", &normalized),
            vec![
                "/usr/local/bin/homeboy".to_string(),
                "rig".to_string(),
                "sources".to_string(),
                "list".to_string(),
            ]
        );
    }

    #[test]
    fn lab_command_preserves_portable_contract_shape() {
        let cli = Cli::parse_from(["homeboy", "lint"]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(command.hot_label, "lint");
        assert!(command.portable);
        assert!(command.unsupported_reason.is_none());
        assert!(command.routing_policy.requires_extension_parity);
    }

    #[test]
    fn extension_update_requires_explicit_lab_runner_without_extension_parity() {
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "lab",
            "extension",
            "update",
            "wordpress",
        ]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(command.hot_label, "extension update");
        assert!(command.portable);
        assert!(!command.routing_policy.default_lab_offload);
        assert!(command.unsupported_reason.is_none());
        assert!(!command.routing_policy.requires_extension_parity);
        assert!(command.required_extensions.is_empty());
        assert!(!command.routing_policy.infer_source_path_tools);
        assert!(cli.command.supports_lab_runner());
    }

    #[test]
    fn extension_update_routes_locally_without_explicit_lab_runner() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let normalized = vec![
            "homeboy".to_string(),
            "extension".to_string(),
            "update".to_string(),
            "wordpress".to_string(),
        ];
        let cli = Cli::parse_from(&normalized);

        let outcome = route_after_parse(&cli, &normalized, None)
            .expect("extension update without --runner should not offload");

        assert_eq!(outcome, None);
        assert!(std::env::var(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV).is_err());
    }

    #[test]
    fn other_extension_commands_stay_local_only() {
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "lab",
            "extension",
            "show",
            "wordpress",
        ]);

        assert!(lab_offload_command(&cli.command).unwrap().is_none());
        assert!(!cli.command.supports_lab_runner());
    }

    #[test]
    fn global_runner_for_runs_show_has_local_mirror_guidance() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "homeboy-lab",
            "runs",
            "show",
            "run-123",
        ]);

        let err = route_after_parse(
            &cli,
            &[
                "homeboy".into(),
                "--runner".into(),
                "homeboy-lab".into(),
                "runs".into(),
                "show".into(),
                "run-123".into(),
            ],
            None,
        )
        .expect_err("runs show rejects global runner with guidance");

        assert_eq!(err.code.as_str(), "validation.invalid_argument");
        assert!(err.message.contains("homeboy runs show run-123"));
        assert!(err.message.contains("without --runner"));
    }

    #[test]
    fn runs_list_runner_option_after_subcommand_routes_locally() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);

        for normalized in [
            vec![
                "homeboy".to_string(),
                "runs".to_string(),
                "list".to_string(),
                "--runner".to_string(),
                "homeboy-lab".to_string(),
                "--status".to_string(),
                "running".to_string(),
                "--limit".to_string(),
                "20".to_string(),
            ],
            vec![
                "homeboy".to_string(),
                "runs".to_string(),
                "list".to_string(),
                "--runner=homeboy-lab".to_string(),
                "--status".to_string(),
                "running".to_string(),
                "--limit".to_string(),
                "20".to_string(),
            ],
        ] {
            let cli = Cli::parse_from(&normalized);

            let outcome = route_after_parse(&cli, &normalized, None)
                .expect("runs list subcommand runner option should not be rejected");

            assert_eq!(outcome, None);
        }
    }

    #[test]
    fn global_runner_for_runs_list_keeps_placement_guidance() {
        let _env = EnvGuard::remove(homeboy::core::observation::LAB_OFFLOAD_METADATA_ENV);
        let normalized = vec![
            "homeboy".to_string(),
            "--runner".to_string(),
            "homeboy-lab".to_string(),
            "runs".to_string(),
            "list".to_string(),
        ];
        let cli = Cli::parse_from(&normalized);

        let err = route_after_parse(&cli, &normalized, None)
            .expect_err("top-level runner on runs list should keep placement guidance");

        assert_eq!(err.code.as_str(), "validation.invalid_argument");
        assert!(err
            .message
            .contains("homeboy runs list --runner homeboy-lab"));
    }

    #[test]
    fn agent_task_inspection_commands_support_runner_resident_recovery() {
        for args in [
            ["homeboy", "agent-task", "status", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "logs", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "artifacts", "agent-task-123"].as_slice(),
            ["homeboy", "agent-task", "review", "agent-task-123"].as_slice(),
        ] {
            let cli = Cli::parse_from(args);
            let command = lab_offload_command(&cli.command).unwrap().unwrap();
            assert_eq!(command.hot_label, "agent-task status/logs/artifacts/review");
            assert_eq!(
                command.source_path_mode,
                runners::LabOffloadSourcePathMode::RunnerResident
            );
            assert_eq!(
                command.workspace_mode_policy,
                runners::LabOffloadWorkspaceModePolicy::RunnerResident
            );
            assert!(!command.routing_policy.default_lab_offload);
        }
    }

    #[test]
    fn agent_task_retry_run_supports_explicit_runner() {
        for args in [
            [
                "homeboy",
                "--runner",
                "homeboy-lab",
                "agent-task",
                "retry",
                "agent-task-123",
                "--run",
            ],
            [
                "homeboy",
                "agent-task",
                "retry",
                "agent-task-123",
                "--run",
                "--runner",
                "homeboy-lab",
            ],
        ] {
            let cli = Cli::parse_from(args);

            let command = lab_offload_command(&cli.command).unwrap().unwrap();

            assert_eq!(cli.runner.as_deref(), Some("homeboy-lab"));
            assert_eq!(
                command.hot_label,
                "agent-task dispatch/cook/loop/run-plan/retry --run"
            );
            assert!(command.portable);
            assert!(command.routing_policy.default_lab_offload);
        }
    }

    #[test]
    fn agent_task_providers_supports_explicit_runner_discovery() {
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "homeboy-lab",
            "agent-task",
            "providers",
        ]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(cli.runner.as_deref(), Some("homeboy-lab"));
        assert_eq!(command.hot_label, "agent-task providers");
        assert!(command.portable);
        assert!(!command.routing_policy.default_lab_offload);
        assert!(!command.routing_policy.requires_extension_parity);
        assert!(command.required_extensions.is_empty());
        assert!(!command.routing_policy.infer_source_path_tools);
    }

    #[test]
    fn tunnel_service_start_supports_explicit_runner_discovery() {
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "homeboy-lab",
            "tunnel",
            "service",
            "start",
            "preview",
            "--cwd",
            "/home/user/Developer/_lab_workspaces/site",
            "--command",
            "npm run dev",
        ]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(cli.runner.as_deref(), Some("homeboy-lab"));
        assert_eq!(command.hot_label, "tunnel service start");
        assert!(command.portable);
        assert!(!command.routing_policy.default_lab_offload);
        assert_eq!(
            command.source_path_mode,
            runners::LabOffloadSourcePathMode::RunnerResident
        );
        assert_eq!(
            command.workspace_mode_policy,
            runners::LabOffloadWorkspaceModePolicy::RunnerResident
        );
        assert!(!command.routing_policy.requires_extension_parity);
        assert!(command.required_extensions.is_empty());
        assert!(!command.routing_policy.infer_source_path_tools);
    }

    #[test]
    fn tunnel_preview_consumer_run_keeps_explicit_runner_contract() {
        let cli = Cli::parse_from([
            "homeboy",
            "--runner",
            "homeboy-lab",
            "tunnel",
            "preview-consumer",
            "run",
            "--config",
            "consumer.json",
            "--preview-public-url",
            "https://preview.example.test/run",
        ]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(command.hot_label, "tunnel preview-consumer run");
        assert!(command.portable);
        assert!(!command.routing_policy.default_lab_offload);
    }

    #[test]
    fn lab_command_with_mutation_flag_stays_portable_for_patch_capture() {
        let cli = Cli::parse_from(["homeboy", "audit", "--baseline"]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(command.hot_label, "audit");
        assert!(command.portable);
        assert_eq!(command.unsupported_reason, None);
        assert!(command.routing_policy.requires_extension_parity);
    }

    #[test]
    fn lab_command_with_ratchet_stays_portable_for_patch_capture() {
        let cli = Cli::parse_from(["homeboy", "audit", "--ratchet"]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(command.hot_label, "audit");
        assert!(command.portable);
        assert_eq!(command.unsupported_reason, None);
        assert!(command.routing_policy.requires_extension_parity);
    }

    #[test]
    fn lab_command_preserves_local_only_contract_shape() {
        let cli = Cli::parse_from(["homeboy", "rig", "up", "demo"]);

        let command = lab_offload_command(&cli.command).unwrap().unwrap();

        assert_eq!(command.hot_label, "rig up");
        assert!(!command.portable);
        assert!(command.unsupported_reason.is_some());
        assert!(!command.routing_policy.requires_extension_parity);
    }

    #[test]
    fn strip_component_target_replaces_positional_with_path() {
        let args = vec![
            "homeboy".to_string(),
            "lint".to_string(),
            "--fix".to_string(),
            "sample-component".to_string(),
        ];

        let rewritten = strip_component_target_args(&args, "sample-component", "/src/sample");

        assert_eq!(
            rewritten,
            vec![
                "homeboy".to_string(),
                "lint".to_string(),
                "--fix".to_string(),
                "--path".to_string(),
                "/src/sample".to_string(),
            ]
        );
    }

    #[test]
    fn strip_component_target_replaces_component_flag_with_path() {
        let args = vec![
            "homeboy".to_string(),
            "refactor".to_string(),
            "--from".to_string(),
            "lint".to_string(),
            "--write".to_string(),
            "--component".to_string(),
            "sample-component".to_string(),
        ];

        let rewritten = strip_component_target_args(&args, "sample-component", "/src/sample");

        assert_eq!(
            rewritten,
            vec![
                "homeboy".to_string(),
                "refactor".to_string(),
                "--from".to_string(),
                "lint".to_string(),
                "--write".to_string(),
                "--path".to_string(),
                "/src/sample".to_string(),
            ]
        );
    }

    #[test]
    fn strip_component_target_only_strips_first_positional_match() {
        // A `--from` value equal to the component id must survive; only the bare
        // positional component token is dropped.
        let args = vec![
            "homeboy".to_string(),
            "refactor".to_string(),
            "--from".to_string(),
            "lint".to_string(),
            "--write".to_string(),
            "dmc".to_string(),
        ];

        let rewritten = strip_component_target_args(&args, "dmc", "/src/dmc");

        assert_eq!(
            rewritten,
            vec![
                "homeboy".to_string(),
                "refactor".to_string(),
                "--from".to_string(),
                "lint".to_string(),
                "--write".to_string(),
                "--path".to_string(),
                "/src/dmc".to_string(),
            ]
        );
    }

    #[test]
    fn strip_component_target_preserves_passthrough_args() {
        let args = vec![
            "homeboy".to_string(),
            "lint".to_string(),
            "--fix".to_string(),
            "dmc".to_string(),
            "--".to_string(),
            "dmc".to_string(),
        ];

        let rewritten = strip_component_target_args(&args, "dmc", "/src/dmc");

        assert_eq!(
            rewritten,
            vec![
                "homeboy".to_string(),
                "lint".to_string(),
                "--fix".to_string(),
                "--".to_string(),
                "dmc".to_string(),
                "--path".to_string(),
                "/src/dmc".to_string(),
            ]
        );
    }

    #[test]
    fn rewrite_component_target_skips_when_path_override_present() {
        let cli = Cli::parse_from([
            "homeboy",
            "lint",
            "--fix",
            "sample-component",
            "--path",
            "/explicit/path",
        ]);
        let normalized = vec![
            "homeboy".to_string(),
            "lint".to_string(),
            "--fix".to_string(),
            "sample-component".to_string(),
            "--path".to_string(),
            "/explicit/path".to_string(),
        ];

        assert!(rewrite_component_target_to_path(&cli.command, &normalized).is_none());
    }

    #[test]
    fn rewrite_component_target_skips_without_component() {
        // No positional component and no --path: source resolves from CWD, so
        // there is nothing to rewrite.
        let cli = Cli::parse_from(["homeboy", "lint", "--fix"]);
        let normalized = vec![
            "homeboy".to_string(),
            "lint".to_string(),
            "--fix".to_string(),
        ];

        assert!(rewrite_component_target_to_path(&cli.command, &normalized).is_none());
    }
}
