pub const DXR_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 DxrCliCommandAdapter {
    pub family: String,
    pub command: String,
    pub normalized_args: Vec<String>,
    pub cache_key: String,
}

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

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

    pub fn explain(&self) -> String {
        format!(
            "dxr family={} command={} args={} cacheKey={}",
            self.family,
            self.command,
            self.normalized_args.len(),
            self.cache_key
        )
    }
}

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

pub fn dxr_cli_config() -> dioprism_cli::DioprismCliIntegrationConfig {
    dioprism_cli::dioprism_cli_config()
        .label("surface", "dxr")
        .tag("dioprism-dxr-cli")
}

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() {
        "assets" | "asset" | "a" => dioprism_assets::cli::run_from(normalize_asset_args(args)),
        "manifest" | "man" | "m" | "resume" => {
            crate::resume::run_from(normalize_manifest_args(args))
        }
        "shader" | "sh" => crate::shader::run_from(normalize_shader_args(args)),
        "dioprism" | "st" | "s" => dioprism_cli::run_from(args),
        "native" | "n" => run_native(args),
        "help" | "--help" | "-h" => {
            println!("{}", help_text());
            Ok(())
        }
        other => Err(format!("unknown command `{other}`\n{}", help_text())),
    }
}

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

fn run_native(mut args: Vec<String>) -> Result<(), String> {
    let Some(command) = args.first().cloned() else {
        println!("{}", native_help_text());
        return Ok(());
    };
    if matches!(command.as_str(), "help" | "--help" | "-h") {
        println!("{}", native_help_text());
        return Ok(());
    }

    match command.as_str() {
        "analyze" | "audit" | "a" => {
            args[0] = "audit".to_string();
            crate::dioprism_native_port::run_from(args)
        }
        "project-convert" | "pc" => {
            args[0] = "convert".to_string();
            crate::dioprism_native_port::run_from(args)
        }
        "plan" | "p" | "convert" | "c" | "build" | "b" | "verify" | "v" => {
            crate::native_port::run_from(args)
        }
        other => Err(format!(
            "unknown native command `{other}`\n{}",
            native_help_text()
        )),
    }
}

fn normalize_asset_args(args: Vec<String>) -> Vec<String> {
    normalize_options(args, &[("--in", "--input"), ("--out", "--output")])
}

fn normalize_manifest_args(mut args: Vec<String>) -> Vec<String> {
    if matches!(args.first().map(String::as_str), Some("route")) {
        args.remove(0);
    }
    normalize_options(args, &[("--output", "--out")])
}

fn normalize_shader_args(mut args: Vec<String>) -> Vec<String> {
    if matches!(args.first().map(String::as_str), Some("render")) {
        args.remove(0);
    }
    args
}

fn normalize_options(args: Vec<String>, aliases: &[(&str, &str)]) -> Vec<String> {
    args.into_iter()
        .map(|arg| {
            aliases
                .iter()
                .find_map(|(from, to)| (arg == *from).then(|| (*to).to_string()))
                .unwrap_or(arg)
        })
        .collect()
}

fn help_text() -> &'static str {
    concat!(
        "dxr <command> [options]\n\n",
        "Commands:\n",
        "  dioprism | st | s <plan|profile|compare|manifest|analyze|audit> ...\n",
        "  assets | asset | a <css|hash|precompress|budget|routes> ...\n",
        "  manifest | man | m route --handler id=/assets/chunk.js#symbol [--out file]\n",
        "  shader | sh render [--preset halo] [--first-paint inline-css]\n",
        "  native | n analyze [path] [--format markdown|json]\n",
        "  native | n plan --repo <path> [--url <url>] [--out dir]\n\n",
        "Examples:\n",
        "  dxr dioprism plan --manifest dioprism.json --route /dioprism\n",
        "  dxr assets css --in public/assets/app.css --out public/assets/app.css\n",
        "  dxr manifest route --handler counter.increment=/assets/counter.js#increment\n",
        "  dxr shader render --preset halo --first-paint inline-css\n",
        "  dxr native analyze .\n",
        "  dxr native plan --repo . --out tmp/native-port"
    )
}

fn native_help_text() -> &'static str {
    concat!(
        "dxr native <command> [options]\n\n",
        "Commands:\n",
        "  analyze | audit | a [path] [--format markdown|json] [--json path] [--markdown path]\n",
        "      Audit the current Dioxus project for native portability.\n",
        "  plan | p --repo <path> [--url <url>] [--html path] [--out dir]\n",
        "      Build a native-port conversion plan.\n",
        "  convert | c --repo <path> --out <workspace> [--url <url>] [--html path]\n",
        "      Generate a native-port workspace when the plan is unblocked.\n",
        "  build | b --workspace <workspace>\n",
        "  verify | v --workspace <workspace>\n",
        "  project-convert | pc [path] [--out-dir path] [--native-css path] [--write]\n",
        "      Use the Dioxus-project conversion helper from dioprism-native-port."
    )
}

fn stable_hash(input: &str) -> String {
    let mut hash: u64 = 0xcbf29ce484222325;
    for byte in input.as_bytes() {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(0x100000001b3);
    }
    format!("{hash:016x}")
}

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

    #[test]
    fn dxr_cli_naming_wrappers_match_existing_apis() {
        let adapter = dxr_cli_command("dioprism", "plan", ["--route", "/dioprism"]);
        assert_eq!(
            adapter,
            DxrCliCommandAdapter::for_command("dioprism", "plan", ["--route", "/dioprism"])
        );
        assert_eq!(
            adapter,
            DxrCliCommandAdapter::new("dioprism", "plan", ["--route", "/dioprism"])
        );
        assert!(run_dxr_from(["help"]).is_ok());
    }

    #[test]
    fn assets_aliases_normalize_short_io_options() {
        let args = normalize_asset_args(vec![
            "css".to_string(),
            "--in".to_string(),
            "in.css".to_string(),
            "--out".to_string(),
            "out.css".to_string(),
        ]);

        assert_eq!(
            args,
            vec!["css", "--input", "in.css", "--output", "out.css"]
        );
    }

    #[test]
    fn manifest_route_wrapper_strips_route_command() {
        let args = normalize_manifest_args(vec![
            "route".to_string(),
            "--handler".to_string(),
            "counter.increment=/assets/counter.js#increment".to_string(),
        ]);

        assert_eq!(
            args,
            vec![
                "--handler",
                "counter.increment=/assets/counter.js#increment"
            ]
        );
    }

    #[test]
    fn shader_render_wrapper_strips_render_command() {
        let args = normalize_shader_args(vec![
            "render".to_string(),
            "--preset".to_string(),
            "halo".to_string(),
        ]);

        assert_eq!(args, vec!["--preset", "halo"]);
    }

    #[test]
    fn top_level_help_is_available() {
        run_from(["help"]).expect("help prints");
    }

    #[test]
    fn dxr_adapter_config_and_coverage_are_public() {
        let adapter = DxrCliCommandAdapter::new(
            "dioprism",
            "plan",
            ["--route", "/dioprism", "--manifest", "dioprism.json"],
        );
        let config = dxr_cli_config().route_enabled("/dioprism", true);

        assert!(config.is_enabled_for_route("/dioprism"));
        assert!(adapter.explain().contains("family=dioprism"));
        assert!(!adapter.cache_key.is_empty());
        assert_eq!(DXR_CLI_INTEGRATION_RANK_COVERAGE.len(), 50);
    }
}
