/// Dispatch core commands (run, serve, inspection, format operations).
///
/// Delegates to sub-dispatchers to keep cyclomatic complexity below 10 per function.
fn dispatch_core_command(cli: &Cli) -> Option<Result<(), CliError>> {
    contract_pre_side_effect_classification!();
    contract_pre_dispatch_completeness!();
    contract_pre_output_format_fidelity!();
    // Try runtime commands first (check, run, serve)
    if let Some(result) = dispatch_runtime_commands(cli) {
        return Some(result);
    }

    // Try inspection commands (inspect, debug, validate, lint, explain, canary)
    if let Some(result) = dispatch_inspection_commands(cli) {
        return Some(result);
    }

    // Try diagnostic commands (trace, tensors, diff)
    if let Some(result) = dispatch_diagnostic_commands(cli) {
        return Some(result);
    }

    // Try format commands (export, import, convert, quantize)
    if let Some(result) = dispatch_format_commands(cli) {
        return Some(result);
    }

    // Try model management commands (merge, finetune, prune, distill, pull, list, rm, tui)
    if let Some(result) = dispatch_model_commands(cli) {
        contract_post_side_effect_classification!(&());
        contract_post_output_format_fidelity!(&());
        return Some(result);
    }

    // Monorepo management (publish, shims, audit, archive) — dev-only
    #[cfg(feature = "dev")]
    if let Commands::Mono(command) = cli.command.as_ref() {
        return Some(crate::commands::mono::run(command.clone()));
    }

    contract_post_side_effect_classification!(&());
    contract_post_output_format_fidelity!(&());
    None
}

/// Dispatch runtime commands: check, run, serve.
fn dispatch_runtime_commands(cli: &Cli) -> Option<Result<(), CliError>> {
    Some(match cli.command.as_ref() {
        // GH-685: forward cli.verbose to check
        Commands::Check { file, no_gpu, json } => crate::error::resolve_model_path(file)
            .and_then(|r| commands::check::run(&r, *no_gpu, *json || cli.json, cli.verbose)),
        Commands::Run {
            source,
            positional_prompt,
            input,
            prompt,
            max_tokens,
            stream,
            language,
            task,
            format,
            no_gpu,
            gpu,
            offline,
            benchmark,
            trace,
            trace_steps,
            trace_verbose,
            trace_output,
            trace_level,
            trace_payload,
            profile,
            temperature,
            top_k,
            top_p,
            seed,
            repeat_penalty,
            repeat_last_n,
            chat,
            split_prompt,
            batch_jsonl,
            verbose,
            backend,
        } => {
            // GH-614: --backend cpu forces CPU-only inference
            let backend_forces_cpu = backend.as_deref() == Some("cpu");
            if let Some(ref b) = backend {
                if b != "cpu" {
                    eprintln!("Backend override: {b}");
                }
            }
            // GH-326: --gpu overrides --no-gpu when both specified
            let effective_no_gpu = if *gpu {
                false
            } else {
                *no_gpu || backend_forces_cpu
            };

            // Batch JSONL mode: load model once, process all prompts
            #[cfg(feature = "inference")]
            if let Some(ref batch_file) = batch_jsonl {
                return Some(run::run_batch(
                    source,
                    batch_file,
                    *max_tokens,
                    *temperature,
                    *top_k,
                    effective_no_gpu,
                    *verbose || cli.verbose,
                ));
            }

            // GH-240: merge global --json flag into output format
            let effective_format = if cli.json { "json" } else { format.as_str() };
            dispatch_run(
                source,
                positional_prompt.as_ref(),
                input.as_deref(),
                prompt.as_ref(),
                *max_tokens,
                *stream,
                language.as_deref(),
                task.as_deref(),
                effective_format,
                effective_no_gpu,
                *offline,
                *benchmark,
                *verbose || cli.verbose,
                *trace,
                *trace_payload,
                trace_steps.as_deref(),
                *trace_verbose,
                trace_output.clone(),
                trace_level.as_str(),
                *profile,
                *chat,
                // PMAT-496: Sampling parameters — no longer silently dropped
                *temperature,
                *top_k,
                *top_p,
                *seed,
                *repeat_penalty,
                *repeat_last_n,
                *split_prompt,
            )
        }

        Commands::Serve { command } => dispatch_serve_command(command, cli),

        // PMAT-182: apr code — sovereign coding assistant
        
        Commands::Code {
            model,
            project,
            resume,
            prompt,
            print,
            max_turns,
            manifest,
            emit_trace,
            output_format,
            input_format,
        } => batuta::agent::code::cmd_code(
            model.clone(),
            project.clone(),
            resume.clone(),
            prompt.clone(),
            *print,
            *max_turns,
            manifest.clone(),
            emit_trace.clone(),
            // PMAT-CODE-OUTPUT-FORMAT-001 / PMAT-CODE-INPUT-FORMAT-001:
            // forward as `&str` so the orchestrate crate need not depend on
            // the apr-cli ValueEnum types.
            match output_format {
                crate::CodeOutputFormat::Text => "text",
                crate::CodeOutputFormat::Json => "json",
            },
            match input_format {
                crate::CodeInputFormat::Text => "text",
                crate::CodeInputFormat::Json => "json",
            },
        )
        .map_err(|e| CliError::Aprender(e.to_string())),

        _ => return None,
    })
}

/// Dispatch inspection commands: inspect, debug, validate, lint, explain, canary.
#[allow(clippy::many_single_char_names)]
fn dispatch_inspection_commands(cli: &Cli) -> Option<Result<(), CliError>> {
    contract_pre_no_side_effects!();
    contract_pre_idempotent_inspection!();
    contract_pre_idempotent_output!();
    let result = match cli.command.as_ref() {
        Commands::Inspect {
            file,
            vocab,
            filters,
            weights,
            json,
        } => {
            let (v, f, w, j) = (*vocab, *filters, *weights, *json || cli.json);
            crate::pipe::with_stdin_support(file, |p| inspect::run(p, v, f, w, j))
        }

        // GH-685: forward cli.verbose to debug
        Commands::Debug {
            file,
            drama,
            hex,
            strings,
            limit,
        } => {
            let (d, h, s, l, j, verb) = (*drama, *hex, *strings, *limit, cli.json, cli.verbose);
            crate::pipe::with_stdin_support(file, |p| debug::run(p, d, h, s, l, j, verb))
        }

        Commands::Validate {
            file,
            quality,
            strict,
            min_score,
        } => {
            let (q, s, ms, j, sc) = (*quality, *strict, *min_score, cli.json, cli.skip_contract);
            crate::pipe::with_stdin_support(file, |p| validate::run(p, q, s, ms, j, sc))
        }

        Commands::ValidateManifest {
            file,
            artifact,
            live,
        } => {
            let live_check = *live && !cli.offline;
            validate_manifest::run(file, artifact.as_deref(), cli.json, live_check)
        }

        Commands::Lint { file } => {
            let (j, q) = (cli.json, cli.quiet);
            crate::pipe::with_stdin_support(file, |p| lint::run(p, j, q))
        }
        Commands::Explain {
            code_or_file,
            file,
            tensor,
            kernel,
            json,
            verbose,
            proof_status,
        } => explain::run(
            code_or_file.clone(),
            file.clone(),
            tensor.as_deref(),
            *kernel,
            *json || cli.json,
            *verbose || cli.verbose,
            *proof_status,
        ),
        Commands::Canary { command } => canary::run(command.clone()),

        _ => return None,
    };
    contract_post_no_side_effects!(&());
    contract_post_idempotent_output!(&());
    Some(result)
}

/// Dispatch diagnostic commands: trace, tensors, diff.
fn dispatch_diagnostic_commands(cli: &Cli) -> Option<Result<(), CliError>> {
    Some(match cli.command.as_ref() {
        Commands::Trace {
            file,
            layer,
            reference,
            json,
            verbose,
            payload,
            diff,
            interactive,
            save_tensor,
            save_tensor_dir,
            save_tensor_layers,
        } => crate::error::resolve_model_path(file).and_then(|r| {
            // SHIP-007 layer-0 stage diff: when --save-tensor is set on a
            // .apr file, dispatch to the end-to-end save-tensor wrapper
            // (PR-A clap → PR-B plan → PR-C-real step1+2 wrapper). For
            // .gguf/.safetensors and the common no-flag case, fall through
            // to the existing trace path.
            #[cfg(feature = "inference")]
            if let Some(stages) = save_tensor.as_deref() {
                let ext_lower = r
                    .extension()
                    .and_then(|e| e.to_str())
                    .map(str::to_ascii_lowercase);
                match ext_lower.as_deref() {
                    Some("apr") => {
                        return crate::commands::trace_save_tensor::run_save_tensor_apr(
                            &r,
                            stages,
                            save_tensor_dir.as_deref(),
                            save_tensor_layers,
                        );
                    }
                    Some("gguf") => {
                        // M-MOE-SUB-2 step (a) CLI completion: GGUF dispatches
                        // to the MoE-traced wireup if the arch is qwen3_moe;
                        // dense-GGUF will be wired in SHIP-007 PR-E.
                        return crate::commands::trace_save_tensor::run_save_tensor_gguf_moe(
                            &r,
                            stages,
                            save_tensor_dir.as_deref(),
                            save_tensor_layers,
                        );
                    }
                    _ => {
                        eprintln!(
                            "apr trace --save-tensor: only .apr and .gguf (qwen3_moe arch) \
                             supported today; .safetensors will be wired in SHIP-007 PR-E \
                             (got {})",
                            r.display()
                        );
                    }
                }
            }
            trace::run(
                &r,
                layer.as_deref(),
                reference.as_deref(),
                *json || cli.json,
                *verbose || cli.verbose,
                *payload,
                *diff,
                *interactive,
            )
        }),

        Commands::Tensors {
            file,
            stats,
            filter,
            limit,
            json,
        } => {
            let (s, f, j, l) = (
                *stats,
                filter.as_deref().map(str::to_owned),
                *json || cli.json,
                *limit,
            );
            crate::pipe::with_stdin_support(file, |p| tensors::run(p, s, f.as_deref(), j, l))
        }

        Commands::Diff {
            file1,
            file2,
            weights,
            values,
            filter,
            limit,
            transpose_aware,
            json,
        } => crate::error::resolve_model_path(file1).and_then(|r1| {
            crate::error::resolve_model_path(file2).and_then(|r2| {
                diff::run(
                    &r1,
                    &r2,
                    *weights,
                    *values,
                    filter.as_deref(),
                    *limit,
                    *transpose_aware,
                    *json || cli.json,
                )
            })
        }),

        _ => return None,
    })
}

/// Dispatch format operation commands: export, import, convert, quantize.
fn dispatch_format_commands(cli: &Cli) -> Option<Result<(), CliError>> {
    Some(match cli.command.as_ref() {
        Commands::Export {
            file,
            format,
            output,
            quantize,
            list_formats,
            batch,
            json,
            plan,
        } => {
            match file
                .as_ref()
                .map(|f| crate::error::resolve_model_path(f))
                .transpose()
            {
                Ok(resolved) => export::run(
                    resolved.as_deref(),
                    format,
                    output.as_deref(),
                    quantize.as_deref(),
                    *list_formats,
                    batch.as_deref(),
                    *json || cli.json,
                    *plan,
                ),
                Err(e) => Err(e),
            }
        }
        Commands::Import {
            source,
            output,
            arch,
            quantize,
            strict,
            preserve_q4k,
            tokenizer,
            enforce_provenance,
            allow_no_config,
        } => {
            // GH-666: Reject network sources when --offline is set
            if cli.offline
                && (source.starts_with("hf://")
                    || source.starts_with("http://")
                    || source.starts_with("https://"))
            {
                return Some(Err(crate::error::CliError::NetworkError(format!(
                    "Cannot import from '{}' in --offline mode. Use a local file path.",
                    source
                ))));
            }
            import::run(
                source,
                output.as_deref(),
                Some(arch.as_str()),
                quantize.as_deref(),
                *strict,
                *preserve_q4k,
                tokenizer.as_ref(),
                *enforce_provenance,
                *allow_no_config,
            )
        }
        Commands::Convert {
            file,
            quantize,
            compress,
            output,
            force,
        } => crate::error::resolve_model_path(file).and_then(|r| {
            convert::run(
                &r,
                quantize.as_deref(),
                compress.as_deref(),
                output,
                *force,
                cli.json,
            )
        }),
        Commands::Stamp {
            file,
            license,
            data_source,
            data_license,
            output,
            force,
        } => crate::error::resolve_model_path(file).and_then(|r| {
            stamp::run(
                &r,
                license.as_deref(),
                data_source.as_deref(),
                data_license.as_deref(),
                output,
                *force,
                cli.json,
            )
        }),
        Commands::Compile {
            file,
            output,
            target,
            quantize,
            release,
            strip,
            lto,
            list_targets,
        } => {
            match file
                .as_ref()
                .map(|f| crate::error::resolve_model_path(f))
                .transpose()
            {
                Ok(resolved) => compile::run(
                    resolved.as_deref(),
                    output.as_deref(),
                    target.as_deref(),
                    quantize.as_deref(),
                    *release,
                    *strip,
                    *lto,
                    *list_targets,
                    cli.json,
                ),
                Err(e) => Err(e),
            }
        }
        Commands::Quantize {
            file,
            scheme,
            output,
            format,
            batch,
            plan,
            force,
        } => crate::error::resolve_model_path(file).and_then(|r| {
            quantize::run(
                &r,
                scheme,
                output.as_deref(),
                format.as_deref(),
                batch.as_deref(),
                *plan,
                *force,
                cli.json,
            )
        }),

        _ => return None,
    })
}

/// Dispatch model management commands: merge, finetune, prune, distill, pull, list, rm, tui.
#[provable_contracts_macros::contract(
    "apr-cli-operations-v1",
    equation = "side_effect_classification"
)]
fn dispatch_model_commands(cli: &Cli) -> Option<Result<(), CliError>> {
    contract_pre_output_path_validation!();
    contract_pre_rm_confirmation_gate!();
    Some(match cli.command.as_ref() {
        Commands::Merge {
            files,
            strategy,
            output,
            weights,
            base_model,
            drop_rate,
            density,
            seed,
            plan,
        } => {
            let resolved: std::result::Result<Vec<std::path::PathBuf>, _> = files
                .iter()
                .map(|f| crate::error::resolve_model_path(f))
                .collect();
            match resolved {
                Ok(r) => merge::run(
                    &r,
                    strategy,
                    output.as_deref(),
                    weights.clone(),
                    base_model.clone(),
                    *drop_rate,
                    *density,
                    *seed,
                    cli.json,
                    *plan,
                ),
                Err(e) => Err(e),
            }
        }
        #[cfg(feature = "training")]
        Commands::Gpu { json } => gpu::run(*json || cli.json),
        #[cfg(feature = "training")]
        Commands::ModelOps(ModelOpsCommands::Finetune {
            file,
            method,
            rank,
            vram,
            plan,
            data,
            output,
            adapter,
            merge,
            epochs,
            learning_rate,
            model_size,
            task,
            num_classes,
            checkpoint_format,
            oversample,
            max_seq_len,
            quantize_nf4,
            gpus,
            gpu_backend,
            role,
            bind,
            coordinator,
            expect_workers,
            wait_gpu,
            adapters,
            adapters_config,
            experimental_mps,
            gpu_share,
            profile,
        }) => {
            if *profile {
                eprintln!("StepProfiler enabled for finetune (PMAT-486)");
            }
            finetune::run(
                file.as_deref(),
                method,
                *rank,
                *vram,
                *plan,
                data.as_deref(),
                output.as_deref(),
                adapter.as_deref(),
                *merge,
                *epochs,
                *learning_rate,
                model_size.as_deref(),
                task.as_deref(),
                *num_classes,
                checkpoint_format,
                *oversample,
                *max_seq_len,
                *quantize_nf4,
                gpus.as_deref(),
                gpu_backend,
                role.as_deref(),
                bind.as_deref(),
                coordinator.as_deref(),
                *expect_workers,
                *wait_gpu,
                adapters,
                adapters_config.as_deref(),
                cli.json,
                *experimental_mps,
                *gpu_share,
            )
        }
        Commands::ModelOps(ModelOpsCommands::Prune {
            file,
            method,
            target_ratio,
            sparsity,
            output,
            remove_layers,
            analyze,
            plan,
            calibration,
        }) => crate::error::resolve_model_path(file).and_then(|r| {
            prune::run(
                &r,
                method,
                *target_ratio,
                *sparsity,
                output.as_deref(),
                remove_layers.as_deref(),
                *analyze,
                *plan,
                calibration.as_deref(),
                cli.json,
            )
        }),
        Commands::ModelOps(ModelOpsCommands::Distill {
            teacher,
            student,
            data,
            output,
            strategy,
            temperature,
            alpha,
            epochs,
            plan,
            config,
            stage,
        }) => distill::run(
            teacher.as_deref(),
            student.as_deref(),
            data.as_deref(),
            output.as_deref(),
            strategy,
            *temperature,
            *alpha,
            *epochs,
            *plan,
            config.as_deref(),
            stage.as_deref(),
            cli.json,
        ),
        Commands::Pull {
            model_ref,
            repo,
            force,
            dry_run,
            revision,
            offline,
            include,
            output,
        } => {
            // SHIP-TWO-001 §26.8: when first positional is the literal
            // "dataset", treat the second positional as the HF dataset
            // repo and dispatch to the dataset puller. Otherwise fall
            // through to the existing model puller (backward compat).
            if model_ref == "dataset" {
                match repo.as_deref() {
                    // Issue #1410 / FALSIFY-PULL-DATASET-009: thread `dry_run`
                    // through to the dataset puller. Previously dropped on
                    // the floor, so `apr pull dataset --dry-run` performed
                    // full downloads in violation of the contract.
                    Some(r) => pull::run_dataset(
                        r,
                        include,
                        revision.as_deref(),
                        output.as_deref(),
                        *dry_run,
                    ),
                    None => Err(crate::error::CliError::ValidationFailed(
                        "apr pull dataset <REPO>: REPO argument required".to_string(),
                    )),
                }
            } else {
                pull::run(model_ref, *force, *dry_run, revision.as_deref(), *offline)
            }
        }
        Commands::Registry { command } => crate::commands::registry::run(command.clone()),
        Commands::List => pull::list(cli.json, cli.quiet),
        Commands::Rm { model_ref } => pull::remove(model_ref),
        Commands::Tui { file } => tui::run(file.clone()),
        Commands::Mcp {} => mcp::run(),

        _ => return None,
    })
}
