use std::{
    fs,
    path::{Path, PathBuf},
};

fn collect_rerun_paths(proto_dir: &Path) -> Vec<PathBuf> {
    let mut out = Vec::new();
    let mut stack = vec![proto_dir.to_path_buf()];

    while let Some(dir) = stack.pop() {
        out.push(dir.clone());

        let entries = fs::read_dir(&dir).unwrap_or_else(|err| {
            panic!("read_dir {}: {}", dir.display(), err);
        });

        for entry in entries {
            let entry = entry.unwrap_or_else(|err| {
                panic!("read_dir entry {}: {}", dir.display(), err);
            });

            let path = entry.path();
            if path.is_dir() {
                stack.push(path);
            } else if path.extension().and_then(|ext| ext.to_str()) == Some("proto") {
                out.push(path);
            }
        }
    }

    out.sort();
    out
}

fn main() {
    let manifest_dir =
        PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));
    let packaged_proto_dir = manifest_dir.join("proto");
    let repo_proto_dir = manifest_dir.join("..").join("..").join("proto");
    let proto_dir = if packaged_proto_dir.join("core.proto").exists() {
        packaged_proto_dir
    } else {
        repo_proto_dir
    };
    let proto_dir = proto_dir.canonicalize().unwrap_or(proto_dir);

    for path in collect_rerun_paths(&proto_dir) {
        println!("cargo:rerun-if-changed={}", path.display());
    }

    let protos = [proto_dir.join("core.proto")];

    let proto_paths: Vec<_> = protos
        .iter()
        .map(|path| path.to_string_lossy().to_string())
        .collect();

    let include_paths = [proto_dir.to_string_lossy().to_string()];

    let mut config = prost_build::Config::new();
    config.type_attribute(".", "#[allow(dead_code)]");
    config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
    config
        .compile_protos(&proto_paths, &include_paths)
        .expect("compile protos");
}
