use std::env;
use std::fs;
use std::io::Write;
use std::path::Path;

extern crate prost_build;
extern crate prost_reflect_build;

fn main() {
    let out_dir = env::var("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("public_resources.rs");
    let mut f = fs::File::create(&dest_path).unwrap();

    // Path relative to manifest dir
    let xml_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/public.xml");

    let mut forward: Vec<(String, u32)> = Vec::new();
    let mut inverse: Vec<(u32, String)> = Vec::new();

    if xml_path.exists() {
        let content: String = fs::read_to_string(&xml_path).unwrap();
        for line in content.lines() {
            let line: &str = line.trim();
            if !line.starts_with("<public") {
                continue;
            }
            // Only attr type
            if !line.contains("type=\"attr\"") {
                continue;
            }
            let name: Option<&str> = extract_attr(line, "name");
            let id_str: Option<&str> = extract_attr(line, "id");
            if let (Some(name), Some(id_str)) = (name, id_str) {
                if id_str.is_empty() {
                    continue;
                }
                let id_str: &str = id_str.trim_start_matches("0x");
                if let Ok(id) = u32::from_str_radix(id_str, 16) {
                    if id != 0 {
                        forward.push((name.to_string(), id));
                        inverse.push((id, name.to_string()));
                    }
                }
            }
        }
    }

    writeln!(f, "// Auto-generated by build.rs").unwrap();
    writeln!(f, "pub static ATTR_FORWARD: &[(&str, u32)] = &[").unwrap();
    for (name, id) in &forward {
        // Escape any special chars in name
        let escaped: String = name.replace('\\', "\\\\").replace('"', "\\\"");
        writeln!(f, "    (\"{}\", 0x{:08x}),", escaped, id).unwrap();
    }
    writeln!(f, "];").unwrap();
    writeln!(f).unwrap();
    writeln!(f, "pub static ATTR_INVERSE: &[(u32, &str)] = &[").unwrap();
    for (id, name) in &inverse {
        let escaped = name.replace('\\', "\\\\").replace('"', "\\\"");
        writeln!(f, "    (0x{:08x}, \"{}\"),", id, escaped).unwrap();
    }
    writeln!(f, "];").unwrap();

    // Re-run if the XML changes
    println!("cargo:rerun-if-changed=src/public.xml");
    println!("cargo:rerun-if-changed=build.rs");

    // Compile axml.proto → Rust types via prost-reflect-build
    // This generates prost message structs AND ReflectMessage impls for native text format.
    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let proto_file = Path::new(&manifest_dir).join("src/proto/axml.proto");
    let proto_include = Path::new(&manifest_dir).join("src/proto");
    prost_reflect_build::Builder::new()
        .file_descriptor_set_path(Path::new(&out_dir).join("axml_descriptor.bin"))
        .file_descriptor_set_bytes("FILE_DESCRIPTOR_SET")
        .compile_protos(&[&proto_file], &[&proto_include])
        .expect("prost_reflect_build::compile_protos failed");
    println!("cargo:rerun-if-changed=src/proto/axml.proto");

    // Generate Python protobuf bindings - only needed when building the Python extension.
    if env::var("CARGO_FEATURE_PYTHON").is_ok() {
        let proto_out_dir = Path::new(&manifest_dir).join("python/pyaxml/proto");
        fs::create_dir_all(&proto_out_dir).expect("failed to create proto output directory");
        let status = std::process::Command::new("protoc")
            .arg(format!("--python_out={}", proto_out_dir.display()))
            .arg(format!("--proto_path={}", proto_include.display()))
            .arg(&proto_file)
            .status()
            .expect(
                "protoc not found, install protobuf-compiler (apt-get install protobuf-compiler)",
            );
        assert!(
            status.success(),
            "protoc --python_out failed with status: {}",
            status
        );
    }
}

fn extract_attr<'a>(line: &'a str, attr: &str) -> Option<&'a str> {
    let needle = format!("{}=\"", attr);
    let start = line.find(needle.as_str())?;
    let start = start + needle.len();
    let end = line[start..].find('"')?;
    Some(&line[start..start + end])
}
