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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=skills/canonical");

    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
    let canonical_root = manifest_dir.join("skills/canonical");
    let out_dir = PathBuf::from(env::var("OUT_DIR")?);
    let out_path = out_dir.join("embedded_skills.rs");

    let mut relative_paths = Vec::new();
    collect_relative_files(&canonical_root, &canonical_root, &mut relative_paths)?;

    let mut generated =
        String::from("pub(crate) static EMBEDDED_SKILL_FILES: &[EmbeddedSkillFile] = &[\n");
    for relative_path in relative_paths {
        let source = canonical_root.join(&relative_path);
        println!("cargo:rerun-if-changed={}", source.display());

        let relative_label = relative_path.to_string_lossy().replace('\\', "/");
        let skill = relative_path
            .components()
            .next()
            .and_then(|component| component.as_os_str().to_str())
            .ok_or("embedded skill path must include a skill directory")?;
        let source_label = source.to_string_lossy().replace('\\', "/");

        generated.push_str(&format!(
            "    EmbeddedSkillFile {{ skill: {skill:?}, relative_path: {relative_label:?}, contents: include_str!({source_label:?}) }},\n",
        ));
    }
    generated.push_str("];\n");

    fs::write(out_path, generated)?;
    Ok(())
}

fn collect_relative_files(
    root: &Path,
    dir: &Path,
    files: &mut Vec<PathBuf>,
) -> std::io::Result<()> {
    let mut entries: Vec<_> = fs::read_dir(dir)?.collect::<std::result::Result<Vec<_>, _>>()?;
    entries.sort_by_key(|entry| entry.file_name());

    for entry in entries {
        let path = entry.path();
        let file_type = entry.file_type()?;

        if file_type.is_dir() {
            collect_relative_files(root, &path, files)?;
            continue;
        }

        if file_type.is_file() {
            let relative = path.strip_prefix(root).map_err(|error| {
                std::io::Error::other(format!(
                    "canonical skill path {} is not under {}: {error}",
                    path.display(),
                    root.display()
                ))
            })?;
            files.push(relative.to_path_buf());
        }
    }

    Ok(())
}
