fn main() {
    // Tell rustc that `#[cfg(bazel)]` is a valid (but never set) cfg key when
    // building via Cargo, so it doesn't emit unexpected_cfgs warnings.
    println!("cargo:rustc-check-cfg=cfg(bazel)");

    // Default builds use pre-generated sources in src/conjure/ and src/proto/;
    // codegen only runs when generate-bindings is explicitly enabled —
    // see the feature docs in Cargo.toml.
    #[cfg(feature = "generate-bindings")]
    generate_all();

    // Without this, Cargo re-runs the build script on every invocation when no
    // rerun-if-changed lines are emitted (default builds don't emit any).
    #[cfg(not(feature = "generate-bindings"))]
    println!("cargo:rerun-if-changed=build.rs");
}

// Everything below is dead code unless generate-bindings is active. Gating with
// #[cfg] keeps conjure-codegen and tonic-build out of the compiled output entirely
// for default builds, so consumers do not pay the build-dep compile cost.

#[cfg(feature = "generate-bindings")]
fn get_protos_recursively(
    root: &std::path::Path,
) -> std::io::Result<Vec<std::path::PathBuf>> {
    let mut protos = Vec::new();
    for entry in std::fs::read_dir(root)? {
        let entry = entry?;
        if entry.path().is_dir() {
            protos.append(&mut get_protos_recursively(&entry.path())?)
        } else if let Some(ext) = entry.path().extension() {
            if ext == "proto" {
                protos.push(entry.path())
            }
        }
    }
    Ok(protos)
}

#[cfg(feature = "generate-bindings")]
fn generate_all() {
    use std::env;
    use std::fs;
    use std::path::{Path, PathBuf};

    let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());

    // Conjure codegen — only when conjure feature is also active
    if env::var("CARGO_FEATURE_CONJURE").is_ok() {
        let conjure_input = Path::new("definitions/conjure/scout-service-api.conjure.json");
        println!("cargo:rerun-if-changed={}", conjure_input.display());

        if !conjure_input.exists() {
            panic!("generate-bindings: {} not found; this is a bug with nominal-api", conjure_input.display());
        }

        conjure_codegen::Config::new()
            .strip_prefix("io.nominal".to_string())
            .generate_files(conjure_input, out_dir.join("conjure"))
            .unwrap();
    }

    // Proto codegen — only when tonic feature is also active
    if env::var("CARGO_FEATURE_TONIC").is_err() {
        return;
    }

    let proto_input = Path::new("definitions/protos/");
    println!("cargo:rerun-if-changed={}", proto_input.display());

    let proto_files = get_protos_recursively(proto_input)
        .unwrap_or_else(|_| panic!("generate-bindings: {} not found; this is a bug with nominal-api", proto_input.display()));

    let proto_out = out_dir.join("proto");
    fs::create_dir_all(&proto_out).unwrap();
    let proto_mod_file = proto_out.join("mod.rs");

    tonic_build::configure()
        .build_server(false)
        .emit_rerun_if_changed(false)
        .compile_well_known_types(true)
        .include_file(&proto_mod_file)
        .out_dir(&proto_out)
        .compile_protos(
            &proto_files,
            &[
                proto_input.to_path_buf(),
                Path::new("definitions/proto-includes/").to_path_buf(),
            ],
        )
        .expect("Failed to compile tonic gRPC stubs from proto definitions");

    // `gen` is a reserved keyword in Rust 2024
    let content = fs::read_to_string(&proto_mod_file).expect("Failed to read generated mod.rs");
    let modified = content.replace("pub mod gen", "pub mod r#gen");
    fs::write(&proto_mod_file, modified).expect("Failed to write modified mod.rs");
}
