use std::{
    collections::BTreeMap,
    ffi::OsString,
    fs,
    path::{Path, PathBuf},
};

use dioprism_core::{
    DioprismBudgetCategory, DioprismDeviceProfile, DioprismManifest, DioprismTelemetryProfile,
};
use dioprism_ssr::{dioprism_demo_manifest, dioprism_runtime_boot_js};
use pico_args::Arguments;
use serde_json::{Value, json};

const DEFAULT_ROUTE: &str = "/dioprism";

pub type DioprismCliIntegrationConfig = dioprism_core::DioprismIntegrationConfig;
pub type DioprismCliRouteOverride = dioprism_core::DioprismRouteOverride;

pub const DIOPRISM_CLI_INTEGRATION_RANK_COVERAGE: &[u8] = &[
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
    27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
];

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DioprismCliCommandAdapter {
    pub command: String,
    pub normalized_args: Vec<String>,
    pub cache_key: String,
    pub labels: BTreeMap<String, String>,
}

impl DioprismCliCommandAdapter {
    pub fn new<I, S>(command: impl Into<String>, args: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        let command = command.into();
        let mut normalized_args = args.into_iter().map(Into::into).collect::<Vec<_>>();
        normalized_args.sort();
        let descriptor = format!(
            "dioprism-cli:adapter:v1\ncommand={command}\nargs={}\n",
            normalized_args.join("\n")
        );
        Self {
            command,
            normalized_args,
            cache_key: dioprism_core::DioprismIntegrationConfig::new("dioprism-cli")
                .cache_key("cli", &descriptor),
            labels: BTreeMap::new(),
        }
    }

    pub fn for_command<I, S>(command: impl Into<String>, args: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        Self::new(command, args)
    }

    pub fn label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.labels.insert(key.into(), value.into());
        self
    }

    pub fn explain(&self) -> dioprism_core::DioprismDiagnostic {
        dioprism_core::DioprismDiagnostic::info(
            "dioprism-cli-adapter",
            "Dioprism CLI command adapter resolved",
        )
        .with_subject(self.command.clone())
        .with_detail("cacheKey", self.cache_key.clone())
        .with_detail("argCount", self.normalized_args.len().to_string())
    }
}

pub fn dioprism_cli_command<I, S>(command: impl Into<String>, args: I) -> DioprismCliCommandAdapter
where
    I: IntoIterator<Item = S>,
    S: Into<String>,
{
    DioprismCliCommandAdapter::for_command(command, args)
}

pub fn dioprism_cli_config() -> DioprismCliIntegrationConfig {
    dioprism_core::DioprismIntegrationConfig::new("dioprism-cli")
}

pub fn dioprism_cli_batch_payload(
    manifest: &DioprismManifest,
) -> dioprism_core::DioprismBatchPayload {
    dioprism_core::DioprismBatchPayload::from_records(
        "dioprism-cli",
        [dioprism_core::dioprism_manifest_record(manifest)
            .label("surface", "cli")
            .count(manifest.routes.len())],
    )
}

pub fn run_from<I, S>(args: I) -> Result<(), String>
where
    I: IntoIterator<Item = S>,
    S: Into<OsString>,
{
    run(args.into_iter().map(Into::into).collect())
}

pub fn run_dioprism_from<I, S>(args: I) -> Result<(), String>
where
    I: IntoIterator<Item = S>,
    S: Into<OsString>,
{
    run_from(args)
}

fn run(mut raw_args: Vec<OsString>) -> Result<(), String> {
    let Some(command) = raw_args.first().cloned() else {
        print_help();
        return Ok(());
    };
    raw_args.remove(0);

    let command = os_string_to_string(command)?;
    let mut args = Arguments::from_vec(raw_args);

    if matches!(command.as_str(), "help" | "--help" | "-h") || args.contains(["-h", "--help"]) {
        print_help();
        return Ok(());
    }

    match command.as_str() {
        "analyze" | "a" => analyze(args),
        "manifest" | "m" => manifest(args),
        "audit" => audit(args),
        "plan" | "p" => plan(args),
        "profile" | "prof" => profile(args),
        "compare" | "cmp" => compare(args),
        other => Err(format!("unknown command `{other}`")),
    }
}

pub fn print_help() {
    println!(concat!(
        "dioprism-cli <command> [options]\n\n",
        "Commands:\n",
        "  manifest | m --route /dioprism [--output file]\n",
        "      Emit a demo Dioprism manifest JSON. Positional route still works.\n",
        "  analyze | a --manifest file\n",
        "      Emit route, island, asset, capability, adapter, and budget summary JSON.\n",
        "  audit --manifest file [--html file]\n",
        "      Check boot size, duplicate assets, server-owned patch policy, and runtime flags.\n",
        "  plan | p --manifest file --route /dioprism [--output file]\n",
        "      Emit deterministic Dioprism V2 optimization-plan JSON.\n",
        "  profile | prof --manifest file [--marks file]\n",
        "      Emit first-pass profiler timing output and optional observed marks.\n",
        "  compare | cmp --baseline file --dioprism file [--json]\n",
        "      Compare two manifest files. Positional route still emits the demo table.\n"
    ));
}

fn analyze(mut args: Arguments) -> Result<(), String> {
    let manifest = load_manifest_source(&mut args, "analyze")?.into_manifest()?;
    let summary = json!({
        "route": primary_route(&manifest),
        "buildId": &manifest.build_id,
        "manifestVersion": &manifest.version,
        "mode": &manifest.mode,
        "routeCount": manifest.routes.len(),
        "routes": manifest.routes.iter().map(|route| json!({
            "id": &route.id,
            "path": &route.path,
            "name": &route.name,
            "assets": &route.assets,
            "islands": &route.islands,
            "capabilities": &route.capabilities,
            "preloads": &route.preloads,
            "prefetches": &route.prefetches,
        })).collect::<Vec<_>>(),
        "islands": manifest.islands.iter().map(|island| serde_json::json!({
            "id": &island.id,
            "priority": &island.priority,
            "hydration": &island.hydration,
            "capabilities": &island.required_capabilities,
        })).collect::<Vec<_>>(),
        "assets": manifest.assets.iter().map(|asset| serde_json::json!({
            "key": &asset.key,
            "sourcePackage": &asset.source_package,
            "kind": &asset.kind,
            "priority": &asset.priority,
            "placement": &asset.placement,
        })).collect::<Vec<_>>(),
        "capabilities": manifest.capabilities.iter().map(|capability| serde_json::json!({
            "id": &capability.id,
            "kind": &capability.kind,
            "trigger": &capability.trigger,
            "assets": &capability.assets,
        })).collect::<Vec<_>>(),
        "plugins": manifest.plugins.iter().map(|plugin| plugin.id.as_str()).collect::<Vec<_>>(),
        "budgetCount": manifest.budgets.len(),
        "budgets": &manifest.budgets,
        "schedulerBudgetMs": manifest.scheduler.budget_ms,
    });
    print_json(&summary)
}

fn manifest(mut args: Arguments) -> Result<(), String> {
    let output = take_path_arg(&mut args, "--output")?;
    let route = take_string_arg(&mut args, "--route")?;
    let route = parse_demo_route("manifest", route, finish_free_args(args)?)?;
    let manifest = dioprism_demo_manifest(route);
    let json = manifest
        .pretty_json()
        .map_err(|error| format!("manifest serialization failed: {error}"))?;

    if let Some(path) = output {
        write_text(&path, &json)?;
    }
    println!("{json}");
    Ok(())
}

fn audit(mut args: Arguments) -> Result<(), String> {
    let html_output = take_path_arg(&mut args, "--html")?;
    let mut manifest = load_manifest_source(&mut args, "audit")?.into_manifest()?;
    let report = audit_report(&mut manifest);

    if let Some(path) = html_output {
        write_text(&path, &audit_html(&report)?)?;
    }

    print_json(&report)
}

fn plan(mut args: Arguments) -> Result<(), String> {
    let output = take_path_arg(&mut args, "--output")?;
    let manifest_path = take_path_arg(&mut args, "--manifest")?;
    let route_arg = take_string_arg(&mut args, "--route")?;
    let free = finish_free_args(args)?;
    let route = if manifest_path.is_some() {
        match (route_arg, free.as_slice()) {
            (Some(route), []) => route,
            (None, []) => String::new(),
            (None, [route]) => route.clone(),
            (Some(_), _) => {
                return Err("plan received both --route and a positional route".to_string());
            }
            (None, _) => return Err("plan expects at most one positional route".to_string()),
        }
    } else {
        parse_demo_route("plan", route_arg, free)?
    };
    let mut manifest = if let Some(path) = manifest_path {
        read_manifest_file(&path)?
    } else {
        dioprism_demo_manifest(route.clone())
    };
    let route = if route.is_empty() {
        primary_route(&manifest)
    } else {
        route
    };
    let plan = manifest.optimize_for_route(&route, DioprismDeviceProfile::balanced(), None);
    let json = serde_json::to_string_pretty(&plan).map_err(|error| error.to_string())?;

    if let Some(path) = output {
        write_text(&path, &json)?;
    }
    println!("{json}");
    Ok(())
}

fn profile(mut args: Arguments) -> Result<(), String> {
    let marks_file = take_path_arg(&mut args, "--marks")?;
    let mut manifest = load_manifest_source(&mut args, "profile")?.into_manifest()?;
    let provided_marks = marks_file.as_deref().map(read_marks_file).transpose()?;
    let telemetry = provided_marks.as_ref().map(|marks| {
        let mut profile = DioprismTelemetryProfile::new("local-profile");
        for mark in marks_value_to_strings(marks) {
            if let Some(asset) = mark.strip_prefix("dioprism:capability:load:start:") {
                profile = profile.with_loaded_asset(asset.to_string());
            }
            profile = profile.with_mark(mark);
        }
        profile
    });
    let route = primary_route(&manifest);
    let plan =
        manifest.optimize_for_route(&route, DioprismDeviceProfile::balanced(), telemetry.clone());

    let mut report = json!({
        "route": route,
        "buildId": &manifest.build_id,
        "marks": default_profile_marks(),
        "islandActivationOrder": manifest.islands.iter().map(|island| island.id.as_str()).collect::<Vec<_>>(),
        "bootRuntimeBytes": dioprism_runtime_boot_js().len(),
        "schedulerBudgetMs": manifest.scheduler.budget_ms,
        "performanceMarksEnabled": manifest.scheduler.performance_marks,
        "optimizationPlanVersion": plan.version,
        "plannedAssetLoads": plan.asset_decisions.iter().filter(|decision| decision.decision.requires_client_load()).count(),
        "routeReadyForInstantNavigation": plan.entry_route_plan().map(|route| route.ready_for_instant_navigation).unwrap_or(false),
    });

    if let Some(provided_marks) = provided_marks {
        report["providedMarks"] = provided_marks;
    }
    if let Some(telemetry) = telemetry {
        report["telemetryProfile"] =
            serde_json::to_value(telemetry).map_err(|error| error.to_string())?;
    }

    print_json(&report)
}

fn compare(mut args: Arguments) -> Result<(), String> {
    let baseline_path = take_path_arg(&mut args, "--baseline")?;
    let dioprism_path = take_path_arg(&mut args, "--dioprism")?;
    let json_output = args.contains("--json");
    let route = take_string_arg(&mut args, "--route")?;
    let free = finish_free_args(args)?;

    let (baseline, dioprism) = match (baseline_path, dioprism_path) {
        (Some(baseline_path), Some(dioprism_path)) => {
            if route.is_some() || !free.is_empty() {
                return Err(
                    "compare with --baseline and --dioprism does not accept a route".to_string(),
                );
            }
            let baseline_manifest = read_manifest_file(&baseline_path)?;
            let dioprism_manifest = read_manifest_file(&dioprism_path)?;
            (
                CompareMetrics::from_manifest(
                    baseline_path.display().to_string(),
                    &baseline_manifest,
                ),
                CompareMetrics::from_manifest(
                    dioprism_path.display().to_string(),
                    &dioprism_manifest,
                ),
            )
        }
        (None, None) => {
            let route = parse_demo_route("compare", route, free)?;
            let manifest = dioprism_demo_manifest(route.clone());
            (
                CompareMetrics::baseline_demo(route),
                CompareMetrics::from_manifest("dioprism demo".to_string(), &manifest),
            )
        }
        _ => {
            return Err("compare requires both --baseline file and --dioprism file".to_string());
        }
    };

    if json_output {
        print_json(&compare_report(&baseline, &dioprism))
    } else {
        print_compare_table(&baseline, &dioprism);
        Ok(())
    }
}

fn audit_report(manifest: &mut DioprismManifest) -> Value {
    let duplicates = manifest.dedupe_assets();
    if manifest.optimization_plan.is_none() {
        let route = primary_route(manifest);
        manifest.optimize_for_route(route, DioprismDeviceProfile::balanced(), None);
    }
    let plan = manifest.optimization_plan.as_ref();
    let boot_size = dioprism_runtime_boot_js().len();
    let boot_budget = manifest.config.full_runtime_budget_bytes;
    let server_owned_safe = manifest
        .server_owned
        .iter()
        .all(|region| region.sanitized && !region.allowed_ops.is_empty());
    let has_manifest_asset = manifest
        .assets
        .iter()
        .any(|asset| asset.key == "dioprism.boot");
    let has_capture_budget = manifest.budgets.iter().any(|budget| {
        matches!(
            budget.policy.category,
            DioprismBudgetCategory::EventCaptureRuntimeSize
        )
    });

    json!({
        "route": primary_route(manifest),
        "buildId": &manifest.build_id,
        "status": if boot_size <= boot_budget && server_owned_safe && has_manifest_asset && has_capture_budget { "pass" } else { "warn" },
        "bootRuntimeBytes": boot_size,
        "bootBudgetBytes": boot_budget,
        "duplicateAssets": duplicates,
        "routeCount": manifest.routes.len(),
        "islandCount": manifest.islands.len(),
        "assetCount": manifest.assets.len(),
        "capabilityCount": manifest.capabilities.len(),
        "budgetCount": manifest.budgets.len(),
        "serverOwnedRegions": manifest.server_owned.len(),
        "manifestAssetPresent": has_manifest_asset,
        "captureBudgetPresent": has_capture_budget,
        "serverOwnedSanitized": server_owned_safe,
        "eventCaptureDestructiveReplay": false,
        "reducedMotionAware": manifest.config.respect_reduced_motion,
        "saveDataAware": manifest.config.respect_save_data,
        "touchDeviceAware": manifest.config.respect_touch_only,
        "optimizationPlanPresent": plan.is_some(),
        "routeReadyForInstantNavigation": plan.and_then(|plan| plan.entry_route_plan()).map(|route| route.ready_for_instant_navigation).unwrap_or(false),
        "plannedClientAssets": plan.map(|plan| plan.asset_decisions.iter().filter(|decision| decision.decision.requires_client_load()).count()).unwrap_or(0),
        "blockingRequests": plan.map(|plan| plan.asset_decisions.iter().filter(|decision| decision.blocking).count()).unwrap_or(0),
    })
}

fn default_profile_marks() -> Vec<&'static str> {
    vec![
        "dioprism:boot:start",
        "dioprism:manifest:read",
        "dioprism:planner:start",
        "dioprism:planner:decision",
        "dioprism:planner:route-ready",
        "dioprism:scheduler:init",
        "dioprism:island:visible:dioprism-visible",
        "dioprism:capability:load:start:shader.runtime",
        "dioprism:capability:load:end:shader.runtime",
        "dioprism:event:capture",
        "dioprism:serverpatch:apply",
    ]
}

enum ManifestSource {
    File(PathBuf),
    DemoRoute(String),
}

impl ManifestSource {
    fn into_manifest(self) -> Result<DioprismManifest, String> {
        match self {
            Self::File(path) => read_manifest_file(&path),
            Self::DemoRoute(route) => Ok(dioprism_demo_manifest(route)),
        }
    }
}

fn load_manifest_source(args: &mut Arguments, command: &str) -> Result<ManifestSource, String> {
    let manifest = take_path_arg(args, "--manifest")?;
    let route = take_string_arg(args, "--route")?;
    let free = finish_free_args(std::mem::replace(args, Arguments::from_vec(Vec::new())))?;

    if let Some(path) = manifest {
        if route.is_some() || !free.is_empty() {
            return Err(format!("{command} with --manifest does not accept a route"));
        }
        return Ok(ManifestSource::File(path));
    }

    Ok(ManifestSource::DemoRoute(parse_demo_route(
        command, route, free,
    )?))
}

fn parse_demo_route(
    command: &str,
    route: Option<String>,
    free: Vec<String>,
) -> Result<String, String> {
    if let Some(route) = route {
        if free.is_empty() {
            return Ok(route);
        }
        return Err(format!(
            "{command} received both --route and a positional route"
        ));
    }

    match free.as_slice() {
        [] => Ok(DEFAULT_ROUTE.to_string()),
        [route] if route.starts_with('-') => Err(format!("unexpected option `{route}`")),
        [route] => Ok(route.clone()),
        _ => Err(format!("{command} expects at most one positional route")),
    }
}

fn take_string_arg(args: &mut Arguments, flag: &'static str) -> Result<Option<String>, String> {
    args.opt_value_from_str(flag)
        .map_err(|error| format!("invalid {flag}: {error}"))
}

fn take_path_arg(args: &mut Arguments, flag: &'static str) -> Result<Option<PathBuf>, String> {
    take_string_arg(args, flag).map(|value| value.map(PathBuf::from))
}

fn finish_free_args(args: Arguments) -> Result<Vec<String>, String> {
    args.finish()
        .into_iter()
        .map(os_string_to_string)
        .collect::<Result<Vec<_>, _>>()
}

fn os_string_to_string(value: OsString) -> Result<String, String> {
    value
        .into_string()
        .map_err(|value| format!("argument is not valid UTF-8: `{}`", value.to_string_lossy()))
}

fn read_manifest_file(path: &Path) -> Result<DioprismManifest, String> {
    let text = fs::read_to_string(path)
        .map_err(|error| format!("failed to read manifest `{}`: {error}", path.display()))?;
    serde_json::from_str(&text)
        .map_err(|error| format!("failed to parse manifest `{}`: {error}", path.display()))
}

fn read_marks_file(path: &Path) -> Result<Value, String> {
    let text = fs::read_to_string(path)
        .map_err(|error| format!("failed to read marks `{}`: {error}", path.display()))?;
    let trimmed = text.trim();
    if trimmed.is_empty() {
        return Ok(Value::Array(Vec::new()));
    }
    if let Ok(value) = serde_json::from_str(trimmed) {
        return Ok(value);
    }
    Ok(Value::Array(
        text.lines()
            .map(str::trim)
            .filter(|line| !line.is_empty())
            .map(|line| Value::String(line.to_string()))
            .collect(),
    ))
}

fn marks_value_to_strings(value: &Value) -> Vec<String> {
    match value {
        Value::Array(items) => items
            .iter()
            .filter_map(|item| match item {
                Value::String(mark) => Some(mark.clone()),
                Value::Object(object) => object
                    .get("name")
                    .and_then(Value::as_str)
                    .map(ToString::to_string),
                _ => None,
            })
            .collect(),
        Value::Object(object) => object
            .get("marks")
            .map(marks_value_to_strings)
            .unwrap_or_default(),
        Value::String(mark) => vec![mark.clone()],
        _ => Vec::new(),
    }
}

fn write_text(path: &Path, text: &str) -> Result<(), String> {
    fs::write(path, text).map_err(|error| format!("failed to write `{}`: {error}", path.display()))
}

fn primary_route(manifest: &DioprismManifest) -> String {
    manifest
        .routes
        .first()
        .map(|route| route.path.clone())
        .unwrap_or_else(|| DEFAULT_ROUTE.to_string())
}

fn print_json(value: &Value) -> Result<(), String> {
    println!(
        "{}",
        serde_json::to_string_pretty(value).map_err(|error| error.to_string())?
    );
    Ok(())
}

fn audit_html(report: &Value) -> Result<String, String> {
    let json = serde_json::to_string_pretty(report).map_err(|error| error.to_string())?;
    Ok(format!(
        concat!(
            "<!doctype html>\n",
            "<html lang=\"en\">\n",
            "<head><meta charset=\"utf-8\"><title>Dioxus Dioprism Audit</title>",
            "<style>body{{font-family:system-ui,sans-serif;margin:2rem;line-height:1.5}}pre{{padding:1rem;background:#f4f4f4;overflow:auto}}</style>",
            "</head>\n",
            "<body><h1>Dioxus Dioprism Audit</h1><pre>{}</pre></body>\n",
            "</html>\n"
        ),
        escape_html(&json)
    ))
}

fn escape_html(value: &str) -> String {
    value
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
}

#[derive(Clone)]
struct CompareMetrics {
    label: String,
    route: String,
    route_count: usize,
    island_count: usize,
    asset_count: usize,
    capability_count: usize,
    budget_count: usize,
    boot_runtime_bytes: usize,
    scheduler_budget_ms: u16,
    duplicate_asset_count: usize,
    blocking_request_count: usize,
    planned_client_asset_count: usize,
    observed_asset_count: usize,
    route_ready_for_instant_navigation: bool,
    budget_overrun_count: usize,
}

impl CompareMetrics {
    fn baseline_demo(route: String) -> Self {
        Self {
            label: "baseline route".to_string(),
            route,
            route_count: 1,
            island_count: 0,
            asset_count: 0,
            capability_count: 0,
            budget_count: 0,
            boot_runtime_bytes: 0,
            scheduler_budget_ms: 0,
            duplicate_asset_count: 0,
            blocking_request_count: 0,
            planned_client_asset_count: 0,
            observed_asset_count: 0,
            route_ready_for_instant_navigation: false,
            budget_overrun_count: 0,
        }
    }

    fn from_manifest(label: String, manifest: &DioprismManifest) -> Self {
        let mut manifest_for_duplicates = manifest.clone();
        let duplicate_asset_count = manifest_for_duplicates.dedupe_assets().len();
        let diagnostics = manifest.validate();
        let budget_overrun_count = diagnostics
            .iter()
            .filter(|diagnostic| diagnostic.code == "budget-overrun")
            .count();
        let plan = manifest.optimization_plan.clone().unwrap_or_else(|| {
            manifest.build_optimization_plan(
                primary_route(manifest),
                DioprismDeviceProfile::balanced(),
                None,
            )
        });
        let blocking_request_count = plan
            .asset_decisions
            .iter()
            .filter(|decision| decision.blocking)
            .count();
        let planned_client_asset_count = plan
            .asset_decisions
            .iter()
            .filter(|decision| decision.decision.requires_client_load())
            .count();
        let observed_asset_count = plan
            .telemetry_profile
            .as_ref()
            .map(|telemetry| telemetry.loaded_assets.len())
            .unwrap_or(0);
        let route_ready_for_instant_navigation = plan
            .entry_route_plan()
            .map(|route| route.ready_for_instant_navigation)
            .unwrap_or(false);

        Self {
            label,
            route: primary_route(manifest),
            route_count: manifest.routes.len(),
            island_count: manifest.islands.len(),
            asset_count: manifest.assets.len(),
            capability_count: manifest.capabilities.len(),
            budget_count: manifest.budgets.len(),
            boot_runtime_bytes: dioprism_runtime_boot_js().len(),
            scheduler_budget_ms: manifest.scheduler.budget_ms,
            duplicate_asset_count,
            blocking_request_count,
            planned_client_asset_count,
            observed_asset_count,
            route_ready_for_instant_navigation,
            budget_overrun_count,
        }
    }

    fn status(&self) -> &'static str {
        if self.duplicate_asset_count > 0 || self.budget_overrun_count > 0 {
            "fail"
        } else if self.blocking_request_count > 2 || !self.route_ready_for_instant_navigation {
            "warn"
        } else {
            "pass"
        }
    }
}

fn compare_report(baseline: &CompareMetrics, dioprism: &CompareMetrics) -> Value {
    json!({
        "baseline": metrics_json(baseline),
        "dioprism": metrics_json(dioprism),
        "status": dioprism.status(),
        "failures": {
            "duplicateAssets": dioprism.duplicate_asset_count,
            "budgetOverruns": dioprism.budget_overrun_count,
        },
        "delta": {
            "routes": signed_delta(baseline.route_count, dioprism.route_count),
            "islands": signed_delta(baseline.island_count, dioprism.island_count),
            "assets": signed_delta(baseline.asset_count, dioprism.asset_count),
            "capabilities": signed_delta(baseline.capability_count, dioprism.capability_count),
            "budgets": signed_delta(baseline.budget_count, dioprism.budget_count),
            "bootRuntimeBytes": signed_delta(baseline.boot_runtime_bytes, dioprism.boot_runtime_bytes),
            "schedulerBudgetMs": signed_delta(
                baseline.scheduler_budget_ms as usize,
                dioprism.scheduler_budget_ms as usize,
            ),
            "blockingRequests": signed_delta(baseline.blocking_request_count, dioprism.blocking_request_count),
            "plannedClientAssets": signed_delta(baseline.planned_client_asset_count, dioprism.planned_client_asset_count),
        }
    })
}

fn metrics_json(metrics: &CompareMetrics) -> Value {
    json!({
        "label": &metrics.label,
        "route": &metrics.route,
        "routes": metrics.route_count,
        "islands": metrics.island_count,
        "assets": metrics.asset_count,
        "capabilities": metrics.capability_count,
        "budgets": metrics.budget_count,
        "bootRuntimeBytes": metrics.boot_runtime_bytes,
        "schedulerBudgetMs": metrics.scheduler_budget_ms,
        "duplicateAssets": metrics.duplicate_asset_count,
        "blockingRequests": metrics.blocking_request_count,
        "plannedClientAssets": metrics.planned_client_asset_count,
        "observedAssets": metrics.observed_asset_count,
        "routeReadyForInstantNavigation": metrics.route_ready_for_instant_navigation,
        "budgetOverruns": metrics.budget_overrun_count,
        "status": metrics.status(),
    })
}

fn signed_delta(baseline: usize, dioprism: usize) -> i64 {
    dioprism as i64 - baseline as i64
}

fn print_compare_table(baseline: &CompareMetrics, dioprism: &CompareMetrics) {
    println!(
        "{:<24} {:>14} {:>14} {:>14}",
        "Metric", "Baseline", "Dioprism", "Delta"
    );
    println!(
        "{:<24} {:>14} {:>14} {:>14}",
        "Route", baseline.route, dioprism.route, "-"
    );
    print_metric("Routes", baseline.route_count, dioprism.route_count);
    print_metric("Islands", baseline.island_count, dioprism.island_count);
    print_metric("Assets", baseline.asset_count, dioprism.asset_count);
    print_metric(
        "Capabilities",
        baseline.capability_count,
        dioprism.capability_count,
    );
    print_metric("Budgets", baseline.budget_count, dioprism.budget_count);
    print_metric(
        "Boot Runtime Bytes",
        baseline.boot_runtime_bytes,
        dioprism.boot_runtime_bytes,
    );
    print_metric(
        "Scheduler Budget Ms",
        baseline.scheduler_budget_ms as usize,
        dioprism.scheduler_budget_ms as usize,
    );
    print_metric(
        "Blocking Requests",
        baseline.blocking_request_count,
        dioprism.blocking_request_count,
    );
    print_metric(
        "Planned Client Assets",
        baseline.planned_client_asset_count,
        dioprism.planned_client_asset_count,
    );
    println!("{:<24} {:>14}", "Dioprism Status", dioprism.status());
}

fn print_metric(label: &str, baseline: usize, dioprism: usize) {
    println!(
        "{:<24} {:>14} {:>14} {:>+14}",
        label,
        baseline,
        dioprism,
        dioprism as isize - baseline as isize
    );
}

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

    #[test]
    fn dioprism_cli_naming_wrappers_match_existing_apis() {
        let adapter = dioprism_cli_command("plan", ["--route", "/dioprism"]);
        assert_eq!(
            adapter,
            DioprismCliCommandAdapter::for_command("plan", ["--route", "/dioprism"])
        );
        assert_eq!(
            adapter,
            DioprismCliCommandAdapter::new("plan", ["--route", "/dioprism"])
        );
        assert!(run_dioprism_from(["help"]).is_ok());
    }
}
