use dioprism_native_core::core::{
    ConversionPlan, GeneratedRouteStatus, NativeAppManifest, NativePortArtifactOptimizer,
    NativePortBaselineReport, NativePortBatchOptions, NativePortDoctorReport,
    NativePortIntegrationOptions, NativePortManifestFragment, NativePortPrecomputedFragments,
    NativePortTraceSink, PageGraph, SiteGraph, Viewport, native_port_baseline_report,
    native_port_doctor_report, native_port_manifest_fragment, native_port_manifest_fragment_traced,
    native_port_optimized_artifacts, native_port_precomputed_fragments,
};
use dioprism_native_core::crawler::{CrawlConfig, crawl_html, crawl_url};
use dioprism_native_core::dioxus::{
    SourceInventoryCache, SourceInventoryFilter, inspect_dioxus_repo_cached,
    merge_dioxus_inventory_into_site,
};
use std::fs;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::{Mutex, OnceLock};

pub type CmdOpts = CommandOptions;

pub fn cmd_opts() -> CommandOptions {
    CommandOptions::default()
}

pub fn run_from<I, S>(args: I) -> Result<(), String>
where
    I: IntoIterator<Item = S>,
    S: Into<String>,
{
    let mut args = args.into_iter().map(Into::into).collect::<Vec<_>>();
    let Some(command) = args.first().cloned() else {
        println!("{}", help_text());
        return Ok(());
    };
    args.remove(0);
    match command.as_str() {
        "audit" | "analyze" | "a" => {
            let options = parse_cli_options(args)?;
            let site = audit(&options)?;
            println!(
                "audited {} page(s), {} unsupported feature(s)",
                site.pages.len(),
                site.unsupported_features.len()
            );
        }
        "plan" | "p" => {
            let options = parse_cli_options(args)?;
            let conversion = plan(&options)?;
            println!(
                "planned {} route(s), {} blocker(s)",
                conversion.generated_routes.len(),
                conversion.blockers.len()
            );
        }
        "convert" | "c" => {
            let options = parse_cli_options(args)?;
            let conversion = convert(&options)?;
            println!(
                "generated native workspace with {} route(s)",
                conversion.generated_routes.len()
            );
        }
        "build" | "b" => {
            let workspace = parse_workspace(args)?;
            build(&workspace)?;
            println!("built `{}`", workspace.display());
        }
        "verify" | "v" => {
            let workspace = parse_workspace(args)?;
            let manifest = verify(&workspace)?;
            println!(
                "verified `{}` with {} route(s)",
                manifest.package_name,
                manifest.routes.len()
            );
        }
        "doctor" | "self-check" | "d" => {
            let options = parse_cli_options(args)?;
            let report = doctor(&options)?;
            println!("{}", report.to_markdown());
        }
        "-h" | "--help" | "help" => println!("{}", help_text()),
        other => return Err(format!("unknown command `{other}`")),
    }
    Ok(())
}

fn parse_cli_options(args: Vec<String>) -> Result<CommandOptions, String> {
    let mut options = CommandOptions::default();
    let mut index = 0;
    while index < args.len() {
        match args[index].as_str() {
            "--url" => {
                index += 1;
                options.url = Some(cli_value(&args, index, "--url")?.to_string());
            }
            "--repo" => {
                index += 1;
                options.repo = Some(PathBuf::from(cli_value(&args, index, "--repo")?));
            }
            "--html" => {
                index += 1;
                options.html = Some(PathBuf::from(cli_value(&args, index, "--html")?));
            }
            "--out" => {
                index += 1;
                options.out = Some(PathBuf::from(cli_value(&args, index, "--out")?));
            }
            other => return Err(format!("unknown option `{other}`")),
        }
        index += 1;
    }
    Ok(options)
}

fn parse_workspace(args: Vec<String>) -> Result<PathBuf, String> {
    if args.is_empty() {
        return Ok(PathBuf::from("."));
    }
    if args.len() == 2 && args[0] == "--workspace" {
        return Ok(PathBuf::from(&args[1]));
    }
    if args.len() == 1 {
        return Ok(PathBuf::from(&args[0]));
    }
    Err("expected `--workspace <path>`".to_string())
}

fn cli_value<'a>(args: &'a [String], index: usize, name: &str) -> Result<&'a str, String> {
    args.get(index)
        .map(String::as_str)
        .ok_or_else(|| format!("{name} requires a value"))
}

pub fn help_text() -> &'static str {
    "native-port\n\nCommands:\n  audit|analyze|a --url <url> --repo <path> [--html <path>] [--out <dir>]\n  plan|p --url <url> --repo <path> [--html <path>] [--out <dir>]\n  convert|c --url <url> --repo <path> --out <workspace> [--html <path>]\n  doctor|self-check|d --url <url> --repo <path> [--html <path>]\n  build|b --workspace <workspace>\n  verify|v --workspace <workspace>"
}

#[derive(Clone, Debug, Default)]
pub struct CommandOptions {
    pub url: Option<String>,
    pub repo: Option<PathBuf>,
    pub html: Option<PathBuf>,
    pub out: Option<PathBuf>,
}

impl CommandOptions {
    pub fn url(mut self, url: impl Into<String>) -> Self {
        self.url = Some(url.into());
        self
    }

    pub fn repo(mut self, repo: impl Into<PathBuf>) -> Self {
        self.repo = Some(repo.into());
        self
    }

    pub fn html(mut self, html: impl Into<PathBuf>) -> Self {
        self.html = Some(html.into());
        self
    }

    pub fn out(mut self, out: impl Into<PathBuf>) -> Self {
        self.out = Some(out.into());
        self
    }
}

pub fn audit(options: &CommandOptions) -> Result<SiteGraph, String> {
    let site = build_site_graph(options)?;
    let out = options
        .out
        .clone()
        .unwrap_or_else(|| PathBuf::from("tmp/native-port"));
    fs::create_dir_all(&out)
        .map_err(|error| format!("failed to create `{}`: {error}", out.display()))?;
    write_json(out.join("native-port-audit.json"), &site)?;
    write_text(out.join("native-port-audit.md"), &site_markdown(&site))?;
    Ok(site)
}

pub fn plan(options: &CommandOptions) -> Result<ConversionPlan, String> {
    let site = build_site_graph(options)?;
    let conversion = ConversionPlan::from_site_graph(site);
    let out = options
        .out
        .clone()
        .unwrap_or_else(|| PathBuf::from("tmp/native-port"));
    fs::create_dir_all(&out)
        .map_err(|error| format!("failed to create `{}`: {error}", out.display()))?;
    write_json(out.join("native-port-plan.json"), &conversion)?;
    write_text(out.join("native-port-plan.md"), &conversion.to_markdown())?;
    Ok(conversion)
}

pub fn convert(options: &CommandOptions) -> Result<ConversionPlan, String> {
    let conversion = plan(options)?;
    if conversion.is_blocked() {
        return Err(format!(
            "native conversion blocked by {} unsupported feature(s); see native-port-plan.md",
            conversion.blockers.len()
        ));
    }
    let out = options
        .out
        .clone()
        .ok_or_else(|| "`convert` requires --out <workspace>".to_string())?;
    write_native_workspace(&out, &conversion)?;
    Ok(conversion)
}

pub fn cli_integration_options(options: &CommandOptions) -> NativePortIntegrationOptions {
    let mut integration = NativePortIntegrationOptions::default();
    integration.package_name = "dioprism-cli".to_string();
    integration.cache_namespace = "dioprism-cli".to_string();
    integration.labels.push("cli".to_string());
    if options.url.is_some() {
        integration
            .tags
            .insert("url".to_string(), "set".to_string());
    }
    if options.repo.is_some() {
        integration
            .tags
            .insert("repo".to_string(), "set".to_string());
    }
    if options.html.is_some() {
        integration
            .tags
            .insert("html".to_string(), "set".to_string());
    }
    integration
}

pub fn manifest_fragment(options: &CommandOptions) -> Result<NativePortManifestFragment, String> {
    let site = build_site_graph(options)?;
    let conversion = ConversionPlan::from_site_graph(site);
    Ok(native_port_manifest_fragment(
        &conversion,
        &cli_integration_options(options),
    ))
}

pub fn manifest_fragment_traced(
    options: &CommandOptions,
    trace: &impl NativePortTraceSink,
) -> Result<NativePortManifestFragment, String> {
    let site = build_site_graph(options)?;
    let conversion = ConversionPlan::from_site_graph(site);
    Ok(native_port_manifest_fragment_traced(
        &conversion,
        &cli_integration_options(options),
        trace,
    ))
}

pub fn plan_many(
    options: &[CommandOptions],
    batch: NativePortBatchOptions,
) -> Result<Vec<ConversionPlan>, String> {
    let chunk_size = batch.chunk_size.max(1);
    if !batch.deterministic_parallel || options.len() <= chunk_size {
        let mut plans = Vec::with_capacity(options.len());
        for chunk in options.chunks(chunk_size) {
            for option in chunk {
                plans.push(plan_without_outputs(option)?);
            }
        }
        return Ok(plans);
    }

    std::thread::scope(|scope| {
        let mut handles = Vec::new();
        for (chunk_index, chunk) in options.chunks(chunk_size).enumerate() {
            handles.push(scope.spawn(move || {
                let plans = chunk
                    .iter()
                    .map(plan_without_outputs)
                    .collect::<Result<Vec<_>, _>>()?;
                Ok::<_, String>((chunk_index, plans))
            }));
        }
        let mut chunks = handles
            .into_iter()
            .map(|handle| {
                handle
                    .join()
                    .map_err(|_| "native-port cli batch worker panicked".to_string())?
            })
            .collect::<Result<Vec<_>, _>>()?;
        chunks.sort_by_key(|(chunk_index, _)| *chunk_index);
        Ok(chunks.into_iter().flat_map(|(_, plans)| plans).collect())
    })
}

pub fn manifest_fragments_many(
    options: &[CommandOptions],
    batch: NativePortBatchOptions,
) -> Result<Vec<NativePortManifestFragment>, String> {
    let plans = plan_many(options, batch)?;
    Ok(plans
        .iter()
        .zip(options.iter())
        .map(|(plan, options)| {
            native_port_manifest_fragment(plan, &cli_integration_options(options))
        })
        .collect())
}

pub fn cli_precomputed_fragments(options: &CommandOptions) -> NativePortPrecomputedFragments {
    native_port_precomputed_fragments(&cli_integration_options(options))
}

pub fn cli_optimized_artifacts(
    options: &CommandOptions,
    optimizer: &dyn NativePortArtifactOptimizer,
) -> Result<Vec<String>, String> {
    let plan = plan_without_outputs(options)?;
    let integration = cli_integration_options(options);
    let fragment = native_port_manifest_fragment(&plan, &integration);
    native_port_optimized_artifacts(&fragment, &integration, optimizer)
        .map_err(|error| format!("failed to optimize cli artifacts: {error}"))
}

pub fn cli_baseline_report(
    scenario: impl Into<String>,
    options: &CommandOptions,
) -> Result<NativePortBaselineReport, String> {
    let plan = plan_without_outputs(options)?;
    Ok(native_port_baseline_report(
        scenario,
        &plan,
        &cli_integration_options(options),
    ))
}

pub fn doctor(options: &CommandOptions) -> Result<NativePortDoctorReport, String> {
    let site = build_site_graph(options)?;
    let conversion = ConversionPlan::from_site_graph(site);
    Ok(native_port_doctor_report(
        &conversion,
        &cli_integration_options(options),
    ))
}

fn plan_without_outputs(options: &CommandOptions) -> Result<ConversionPlan, String> {
    build_site_graph(options).map(ConversionPlan::from_site_graph)
}

pub fn build(workspace: &Path) -> Result<(), String> {
    let status = Command::new("cargo")
        .arg("build")
        .current_dir(workspace)
        .status()
        .map_err(|error| {
            format!(
                "failed to run cargo build in `{}`: {error}",
                workspace.display()
            )
        })?;
    if status.success() {
        Ok(())
    } else {
        Err(format!("cargo build failed in `{}`", workspace.display()))
    }
}

pub fn verify(workspace: &Path) -> Result<NativeAppManifest, String> {
    let manifest_path = workspace.join("native-port-manifest.json");
    let source = fs::read_to_string(&manifest_path)
        .map_err(|error| format!("failed to read `{}`: {error}", manifest_path.display()))?;
    let manifest: NativeAppManifest = serde_json::from_str(&source)
        .map_err(|error| format!("failed to parse `{}`: {error}", manifest_path.display()))?;
    verify_manifest(workspace, manifest)
}

pub fn verify_manifest(
    workspace: &Path,
    manifest: NativeAppManifest,
) -> Result<NativeAppManifest, String> {
    let missing_routes = manifest
        .routes
        .iter()
        .filter(|route| route.status != GeneratedRouteStatus::Ready)
        .map(|route| route.route.clone())
        .collect::<Vec<_>>();
    if !missing_routes.is_empty() {
        return Err(format!(
            "workspace contains blocked routes: {}",
            missing_routes.join(", ")
        ));
    }
    for required in [
        "Cargo.toml",
        "src/main.rs",
        "src/generated_routes.rs",
        "native-port.css",
    ] {
        let path = workspace.join(required);
        if !path.exists() {
            return Err(format!("workspace is missing `{}`", path.display()));
        }
    }
    Ok(manifest)
}

pub fn build_site_graph(options: &CommandOptions) -> Result<SiteGraph, String> {
    let viewports = vec![Viewport::desktop(), Viewport::mobile()];
    let mut site = if let Some(html_path) = &options.html {
        let html = fs::read_to_string(html_path)
            .map_err(|error| format!("failed to read `{}`: {error}", html_path.display()))?;
        let url = options.url.as_deref().unwrap_or("file:///index.html");
        crawl_html(url, &html, &viewports)?
    } else if let Some(url) = &options.url {
        crawl_url(&CrawlConfig {
            url: url.clone(),
            max_pages: 1,
            viewports,
        })?
    } else {
        SiteGraph::new(None)
    };

    if let Some(repo) = &options.repo {
        static SOURCE_CACHE: OnceLock<Mutex<SourceInventoryCache>> = OnceLock::new();
        let mut cache = SOURCE_CACHE
            .get_or_init(|| Mutex::new(SourceInventoryCache::default()))
            .lock()
            .map_err(|_| "source inventory cache lock is poisoned".to_string())?;
        let inventory =
            inspect_dioxus_repo_cached(repo, SourceInventoryFilter::default(), &mut cache)?;
        if site.pages.is_empty() {
            for route in &inventory.routes {
                let mut page = PageGraph::new(
                    route.path.clone(),
                    if route.path.is_empty() {
                        "repo:///".to_string()
                    } else {
                        format!("repo://{}", route.path)
                    },
                );
                page.title = route.component.clone();
                site.add_page(page);
            }
        }
        site = merge_dioxus_inventory_into_site(site, inventory);
    }

    site.normalize_for_output();
    Ok(site)
}

pub fn write_native_workspace(out: &Path, conversion: &ConversionPlan) -> Result<(), String> {
    let src = out.join("src");
    fs::create_dir_all(&src)
        .map_err(|error| format!("failed to create `{}`: {error}", src.display()))?;

    write_text(out.join("Cargo.toml"), &generated_cargo_toml(out)?)?;
    write_text(src.join("main.rs"), GENERATED_MAIN)?;
    write_text(
        src.join("generated_routes.rs"),
        &generated_routes_rs(conversion),
    )?;
    write_text(
        out.join("native-port.css"),
        &generated_native_css(conversion),
    )?;
    write_json(out.join("native-port-manifest.json"), &conversion.manifest)?;
    write_json(out.join("native-port-report.json"), conversion)?;
    write_text(out.join("native-port-report.md"), &conversion.to_markdown())?;
    Ok(())
}

fn generated_cargo_toml(out: &Path) -> Result<String, String> {
    let repo_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(Path::parent)
        .ok_or_else(|| "failed to resolve native-port repo root".to_string())?
        .to_path_buf();
    let core_path = normalize_path(&repo_root.join("crates/dioprism-native-core"));
    let native_path = normalize_path(&repo_root.join("crates/dioprism-native-core"));
    let package_name = out
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("native-port-app")
        .chars()
        .map(|ch| {
            if ch.is_ascii_alphanumeric() || ch == '-' {
                ch
            } else {
                '-'
            }
        })
        .collect::<String>();
    Ok(format!(
        r#"[package]
name = "{package_name}"
version = "0.1.0"
edition = "2024"

[dependencies]
dioxus = {{ version = "0.8.0-alpha.0", default-features = false, features = ["document", "minimal", "native"] }}
dioprism-native-core = {{ path = "{core_path}" }}
dioprism-native-core = {{ path = "{native_path}" }}
serde_json = "1.0.150"

[workspace]
"#
    ))
}

const GENERATED_MAIN: &str = r#"mod generated_routes;

use dioxus::prelude::*;
use dioprism_native_core::native::{FilesystemRuntimeAdapter, NativeRuntimeAdapter};

fn main() {
    let manifest: dioprism_native_core::core::NativeAppManifest =
        serde_json::from_str(include_str!("../native-port-manifest.json"))
            .expect("generated native-port manifest is valid");
    let adapter = FilesystemRuntimeAdapter::for_manifest(&manifest, ".native-port");
    let info = adapter.system_info();
    println!(
        "native-port app '{}' using {} renderer: {} route(s) on {}/{}",
        manifest.package_name,
        manifest.renderer,
        generated_routes::ROUTES.len(),
        info.os,
        info.arch
    );
    dioxus::launch(App);
}

const NATIVE_PORT_CSS: &str = include_str!("../native-port.css");

#[component]
fn App() -> Element {
    let manifest: dioprism_native_core::core::NativeAppManifest =
        serde_json::from_str(include_str!("../native-port-manifest.json"))
            .expect("generated native-port manifest is valid");
    rsx! {
        document::Title { "Native Port App" }
        document::Style { "{NATIVE_PORT_CSS}" }
        main { class: "native-port-shell",
            h1 { "Native Port App" }
            p { "Renderer: {manifest.renderer}" }
            for (route, component) in generated_routes::ROUTES {
                section { class: "native-port-route",
                    h2 { "{route}" }
                    p { "Generated component: {component}" }
                }
            }
        }
    }
}
"#;

fn generated_routes_rs(conversion: &ConversionPlan) -> String {
    let mut out = String::from("pub const ROUTES: &[(&str, &str)] = &[\n");
    for route in &conversion.generated_routes {
        out.push_str(&format!(
            "    ({:?}, {:?}),\n",
            route.route, route.component_name
        ));
    }
    out.push_str("];\n");
    out
}

fn generated_native_css(conversion: &ConversionPlan) -> String {
    let mut css = String::from("/* Generated by native-port convert. */\n");
    css.push_str(
        "html, body { margin: 0; min-height: 100%; background: #020617; color: #f8fafc; font-family: Inter, system-ui, sans-serif; }\n",
    );
    css.push_str(".native-port-shell { min-height: 100vh; padding: 24px; }\n");
    css.push_str(".native-port-route { border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; padding: 18px; margin-bottom: 12px; }\n");
    for route in &conversion.generated_routes {
        css.push_str(&format!(
            "/* route {} -> {} */\n",
            route.route, route.component_name
        ));
    }
    css
}

fn write_json(path: impl AsRef<Path>, value: &impl serde::Serialize) -> Result<(), String> {
    let path = path.as_ref();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)
            .map_err(|error| format!("failed to create `{}`: {error}", parent.display()))?;
    }
    let file = fs::File::create(path)
        .map_err(|error| format!("failed to write `{}`: {error}", path.display()))?;
    serde_json::to_writer_pretty(BufWriter::new(file), value)
        .map_err(|error| format!("failed to encode JSON `{}`: {error}", path.display()))
}

fn write_text(path: impl AsRef<Path>, text: &str) -> Result<(), String> {
    let path = path.as_ref();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)
            .map_err(|error| format!("failed to create `{}`: {error}", parent.display()))?;
    }
    fs::write(path, text).map_err(|error| format!("failed to write `{}`: {error}", path.display()))
}

fn site_markdown(site: &SiteGraph) -> String {
    let mut out = String::new();
    out.push_str("# Native Port Audit\n\n");
    out.push_str(&format!(
        "- Origin: {}\n",
        site.origin.as_deref().unwrap_or("unknown")
    ));
    out.push_str(&format!("- Pages: {}\n", site.pages.len()));
    out.push_str(&format!(
        "- Unsupported features: {}\n\n",
        site.unsupported_features.len()
    ));
    out.push_str("## Routes\n\n");
    for route in &site.route_graph.routes {
        out.push_str(&format!("- `{}`\n", route.path));
    }
    out
}

fn normalize_path(path: &Path) -> String {
    path.display().to_string().replace('\\', "/")
}

pub mod prelude {
    pub use crate::native_port::{
        CmdOpts, CommandOptions, audit, build, build_site_graph, cli_baseline_report,
        cli_integration_options, cli_optimized_artifacts, cli_precomputed_fragments, cmd_opts,
        convert, doctor, manifest_fragment, manifest_fragment_traced, manifest_fragments_many,
        plan, plan_many, verify, verify_manifest, write_native_workspace,
    };
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::time::{SystemTime, UNIX_EPOCH};

    #[test]
    fn convert_writes_ready_to_build_workspace_for_supported_html() {
        let root = fixture_root("convert-supported");
        fs::create_dir_all(&root).unwrap();
        let html = root.join("index.html");
        fs::write(
            &html,
            r#"<title>Demo</title><main><h1>Hello</h1><a href="/docs">Docs</a></main>"#,
        )
        .unwrap();
        let out = root.join("app");
        let options = CommandOptions {
            url: Some("https://example.test/".to_string()),
            html: Some(html),
            out: Some(out.clone()),
            ..CommandOptions::default()
        };

        let conversion = convert(&options).unwrap();

        assert!(!conversion.is_blocked());
        assert!(out.join("Cargo.toml").exists());
        assert!(out.join("src/generated_routes.rs").exists());
        assert!(verify(&out).is_ok());
        assert!(verify_manifest(&out, conversion.manifest.clone()).is_ok());
        let _ = fs::remove_dir_all(root);
    }

    #[test]
    fn convert_hard_fails_unsupported_canvas() {
        let root = fixture_root("convert-blocked");
        fs::create_dir_all(&root).unwrap();
        let html = root.join("index.html");
        fs::write(&html, r#"<canvas></canvas>"#).unwrap();
        let out = root.join("app");
        let options = CommandOptions {
            url: Some("https://example.test/".to_string()),
            html: Some(html),
            out: Some(out.clone()),
            ..CommandOptions::default()
        };

        let err = convert(&options).unwrap_err();

        assert!(err.contains("native conversion blocked"));
        assert!(!out.join("Cargo.toml").exists());
        assert!(
            root.join("app/native-port-plan.json").exists()
                || root.join("tmp/native-port/native-port-plan.json").exists()
        );
        let _ = fs::remove_dir_all(root);
    }

    #[test]
    fn repo_only_dioxus_routes_become_pages() {
        let root = fixture_root("repo-only");
        fs::create_dir_all(root.join("src")).unwrap();
        fs::write(
            root.join("Cargo.toml"),
            r#"[package]
name = "demo"
version = "0.1.0"
[dependencies]
dioxus = "0.7"
"#,
        )
        .unwrap();
        fs::write(
            root.join("src/main.rs"),
            r#"
#[derive(Routable)]
enum Route {
    #[route("/")]
    Home {},
}
"#,
        )
        .unwrap();

        let site = build_site_graph(&CommandOptions {
            repo: Some(root.clone()),
            ..CommandOptions::default()
        })
        .unwrap();

        assert!(site.pages.iter().any(|page| page.route == "/"));
        assert!(
            site.source_inventory
                .frameworks
                .iter()
                .any(|framework| framework.name == "dioxus")
        );
        let _ = fs::remove_dir_all(root);
    }

    #[test]
    fn plan_output_is_deterministic_for_dry_runs() {
        let root = fixture_root("plan-deterministic");
        fs::create_dir_all(&root).unwrap();
        let html = root.join("index.html");
        fs::write(
            &html,
            r#"<a href="/z">Z</a><a href="/a">A</a><img src="/hero.png"><script src="/app.js"></script>"#,
        )
        .unwrap();
        let out = root.join("out");
        let options = CommandOptions {
            url: Some("https://example.test/".to_string()),
            html: Some(html),
            out: Some(out.clone()),
            ..CommandOptions::default()
        };

        let first = plan(&options).unwrap();
        let first_json = fs::read_to_string(out.join("native-port-plan.json")).unwrap();
        let second = plan(&options).unwrap();
        let second_json = fs::read_to_string(out.join("native-port-plan.json")).unwrap();

        assert_eq!(first, second);
        assert_eq!(first_json, second_json);
        assert!(
            first_json.find("\"to\": \"/a\"").unwrap() < first_json.find("\"to\": \"/z\"").unwrap()
        );
        let _ = fs::remove_dir_all(root);
    }

    #[test]
    fn doctor_and_manifest_fragment_use_public_cli_options() {
        let root = fixture_root("doctor");
        fs::create_dir_all(&root).unwrap();
        let html = root.join("index.html");
        fs::write(
            &html,
            r#"<title>Home</title><a href="/docs">Docs</a><link rel="stylesheet" href="/app.css">"#,
        )
        .unwrap();
        let options = CommandOptions {
            url: Some("https://example.test/".to_string()),
            html: Some(html),
            ..CommandOptions::default()
        };

        let report = doctor(&options).unwrap();
        let fragment = manifest_fragment(&options).unwrap();

        assert!(report.ok());
        assert_eq!(fragment.package_name, "dioprism-cli");
        assert_eq!(fragment.routes[0].route, "/");
        assert_eq!(fragment.tags.get("html").map(String::as_str), Some("set"));
        let _ = fs::remove_dir_all(root);
    }

    #[test]
    fn cli_batch_trace_and_baseline_helpers_do_not_require_output_writes() {
        let root = fixture_root("batch");
        fs::create_dir_all(&root).unwrap();
        let home = root.join("home.html");
        let docs = root.join("docs.html");
        fs::write(&home, r#"<title>Home</title><a href="/docs">Docs</a>"#).unwrap();
        fs::write(&docs, r#"<title>Docs</title><a href="/">Home</a>"#).unwrap();
        let options = [
            CommandOptions {
                url: Some("https://example.test/".to_string()),
                html: Some(home),
                ..CommandOptions::default()
            },
            CommandOptions {
                url: Some("https://example.test/docs".to_string()),
                html: Some(docs),
                ..CommandOptions::default()
            },
        ];

        let plans = plan_many(
            &options,
            NativePortBatchOptions::default()
                .chunk_size(1)
                .deterministic_parallel(true),
        )
        .unwrap();
        let fragments = manifest_fragments_many(
            &options,
            NativePortBatchOptions::default()
                .chunk_size(1)
                .deterministic_parallel(true),
        )
        .unwrap();
        let trace_events = std::cell::RefCell::new(Vec::new());
        let traced = manifest_fragment_traced(&options[0], &|event| {
            trace_events.borrow_mut().push(event);
        })
        .unwrap();
        let precomputed = cli_precomputed_fragments(&options[0]);
        let optimized = cli_optimized_artifacts(
            &options[0],
            &dioprism_native_core::core::WhitespaceNativePortArtifactOptimizer,
        )
        .unwrap();
        let baseline = cli_baseline_report("cli-fixture", &options[0]).unwrap();

        assert_eq!(plans.len(), 2);
        assert_eq!(fragments.len(), 2);
        assert_eq!(fragments[0].package_name, "dioprism-cli");
        assert_eq!(traced.package_name, "dioprism-cli");
        assert_eq!(trace_events.borrow().len(), 2);
        assert!(precomputed.runtime_marker.contains("dioprism-cli"));
        assert!(
            optimized
                .iter()
                .any(|artifact| artifact.contains("dioprism-cli"))
        );
        assert!(baseline.output_bytes > 0);
        assert!(!root.join("tmp/native-port").exists());
        let _ = fs::remove_dir_all(root);
    }

    fn fixture_root(name: &str) -> PathBuf {
        let unique = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        std::env::temp_dir().join(format!("dioprism-cli-{name}-{unique}"))
    }
}
