//! Emits `Cargo.toml` and `build.rs` for the swift-bridge crate.

/// Formats a features array for TOML output.
/// Uses multi-line format when `features.len() >= 3` or the rendered line exceeds 100 chars.
fn format_features_array(features: &[String]) -> String {
    if features.is_empty() {
        return String::new();
    }

    // Try single-line format first.
    let quoted = features.iter().map(|f| format!("\"{f}\"")).collect::<Vec<_>>();
    let single_line = quoted.join(", ");
    let single_line_full = format!(", features = [{single_line}]");

    // Use multi-line if we have 3+ features or the line would exceed 100 chars.
    if features.len() >= 3 || single_line_full.len() > 100 {
        let mut multi_line = String::from(", features = [\n");
        for feature in &quoted {
            multi_line.push_str("    ");
            multi_line.push_str(feature);
            multi_line.push_str(",\n");
        }
        multi_line.push(']');
        multi_line
    } else {
        single_line_full
    }
}

/// Emit the `Cargo.toml` content for the generated swift crate.
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_cargo_toml(
    crate_name: &str,
    core_dep_key: &str,
    _core_crate_dir: &str,
    version: &str,
    swift_bridge_ver: &str,
    swift_bridge_build_ver: &str,
    core_path: &str,
    features: &[String],
    extra_deps: &str,
    license: &str,
    has_streaming_adapters: bool,
) -> String {
    let source_crate_name = core_dep_key;
    let features_block = if features.is_empty() {
        String::new()
    } else {
        format_features_array(features)
    };
    // When the Rust ident form of the umbrella crate name (`core_dep_key`,
    // e.g. `sample_llm`) differs from the actual cargo package name in the
    // umbrella Cargo.toml (`crate_name`, e.g. `sample-llm`), cargo will not
    // resolve the path dependency unless we add an explicit `package = "..."`
    // rename. Use `crate_name` (the [[crates]] `name` field, which is the
    // cargo package name) rather than `core_crate_dir` (the directory name)
    // because the two can differ — e.g. `[[crates]] name = "sample-markdown-rs"`
    // with sources under `crates/sample-markdown/` where the package on disk
    // is `sample-markdown-rs` but the directory is `sample-markdown`.
    let package_rename_block = if core_dep_key != crate_name {
        format!(", package = \"{crate_name}\"")
    } else {
        String::new()
    };
    // Streaming adapter shims use `futures_util::StreamExt`, so the dep is
    // required only when the crate config declares streaming adapters.
    let streaming_deps = if has_streaming_adapters {
        "futures-util = \"0.3\"\n"
    } else {
        ""
    };
    let extra_deps_block = if extra_deps.trim().is_empty() {
        String::new()
    } else {
        format!("{extra_deps}\n")
    };
    // Emit the core-facade dep in dual form (`{ version = "...", path = "..." }`)
    // so in-repo dev path builds keep working while cargo-package flows can
    // strip the path to a registry version-dep. Features + the optional
    // `package = "..."` rename are appended as the inline-table suffix.
    let core_dep = crate::scaffold::render_core_dep(
        source_crate_name,
        core_path,
        &format!("{features_block}{package_rename_block}"),
        version,
    );
    // Build [dependencies] block alphabetically sorted to match cargo-sort.
    // Order: ahash, async-trait, futures-util?, <core-crate>,
    // libc, serde, serde_json, swift-bridge, tokio.
    let mut dep_entries: Vec<String> = vec![
        "ahash = \"0.8\"".to_string(),
        "async-trait = \"0.1\"".to_string(),
        "libc = \"0.2\"".to_string(),
        "serde = { version = \"1\", features = [\"derive\"] }".to_string(),
        "serde_json = \"1\"".to_string(),
        format!("swift-bridge = \"{swift_bridge_ver}\""),
        "tokio = { version = \"1\", features = [\"rt\", \"rt-multi-thread\", \"macros\"] }".to_string(),
    ];
    if !core_dep.is_empty() {
        dep_entries.push(core_dep.clone());
    }
    if has_streaming_adapters {
        dep_entries.push("futures-util = \"0.3\"".to_string());
    }
    for line in extra_deps.lines() {
        let trimmed = line.trim_end();
        if !trimmed.is_empty() {
            dep_entries.push(trimmed.to_string());
        }
    }
    dep_entries.sort();
    let dep_block = dep_entries.join("\n");
    let _ = streaming_deps;
    let _ = extra_deps_block;

    format!(
        r#"# Generated by alef. Do not edit by hand.
[package]
name = "{crate_name}-swift"
version = "{version}"
edition = "2024"
license = "{license}"

# `ahash`, `async-trait`, `libc`, `serde`, `serde_json`, and `tokio` are all
# conditionally referenced by alef-emitted code: `ahash` only when the
# umbrella crate exposes `AHashMap<Cow<str>, _>` parameters (the conditional
# `__*_ahash` shim rebuilds), `async-trait` and `tokio` only when the API
# surface includes async streaming adapters and runtime spawn, `libc` only
# when service API C callback functions are emitted, `serde` and
# `serde_json` only when JSON DTO conversions are emitted. They are listed
# unconditionally in `[dependencies]` so the manifest is stable across
# regens, and ignored here so cargo-machete does not flag downstream crates
# whose API surface does not trigger those paths as unused.
[package.metadata.cargo-machete]
ignored = ["ahash", "async-trait", "libc", "serde", "serde_json", "tokio"]

[lib]
crate-type = ["cdylib", "staticlib"]
# The `extern "Swift"` block emits linker references that are only resolvable
# when the crate is linked into a Swift target. `cargo test --workspace` on
# pure-Rust runners (e.g. windows-latest) would otherwise fail with
# undefined `__swift_bridge__$*$alef_visit_*` symbols.
test = false
doctest = false
bench = false

[dependencies]
{dep_block}

[build-dependencies]
swift-bridge-build = "{swift_bridge_build_ver}"
"#
    )
}

/// Emit the `build.rs` content for the generated swift crate.
pub(crate) fn emit_build_rs() -> String {
    r#"// Generated by alef. Do not edit by hand.
use std::path::PathBuf;

fn main() {
    let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR unset"));
    let crate_name = std::env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME unset");
    let bridges = vec!["src/lib.rs"];
    swift_bridge_build::parse_bridges(bridges).write_all_concatenated(out_dir, &crate_name);
    println!("cargo:rerun-if-changed=src/lib.rs");
}
"#
    .to_string()
}
