// build.rs — auto-generates src/modules/registry.rs
//
// Scans src/modules/ for subdirectories containing a mod.rs file.
// Each subdirectory name like "home" maps to a struct "HomeModule"
// defined in src/modules/home/mod.rs.
//
// Rules for a directory to be picked up:
//   • Located directly in src/modules/
//   • Contains a mod.rs file
//   • Does NOT contain plugin.toml (add-ons register via BuiltinPlugin::ribbon)
//   • Defines a pub struct {PascalCase}Module implementing CadModule
//     (unit struct — no ::new() needed)
//
// Tab display order: the PRIORITY list below controls order.
// Unlisted modules appear after priority modules, sorted alphabetically.
//
// Adding a new module:
//   1. Create src/modules/my_name/mod.rs  (implement CadModule as MyNameModule)
//   2. Add `pub mod my_name;` to src/modules/mod.rs
//   3. cargo build  →  module appears in the ribbon

use std::fs;
use std::path::Path;

// Display order for known modules; unknown modules follow alphabetically.
const PRIORITY: &[&str] = &["draw", "model", "insert", "annotate", "view", "manage"];

fn main() {
    // Windows: embed AppIcon.ico into the .exe so the executable carries its
    // own icon (Explorer, taskbar, Start-menu tile, file associations). The
    // .ico is produced from assets/logo.svg by the release workflow before the
    // build; when it is absent (local/dev builds) this is skipped. See #107.
    #[cfg(windows)]
    {
        println!("cargo:rerun-if-changed=packaging/windows/AppIcon.ico");
        if Path::new("packaging/windows/AppIcon.ico").exists() {
            let mut res = winresource::WindowsResource::new();
            res.set_icon("packaging/windows/AppIcon.ico");
            if let Err(e) = res.compile() {
                println!("cargo:warning=failed to embed Windows icon: {e}");
            }
        }
    }

    let mods_dir = Path::new("src/modules");
    println!("cargo:rerun-if-changed=src/modules");

    // Collect subdirectories that contain mod.rs.
    let mut modules: Vec<String> = fs::read_dir(mods_dir)
        .expect("src/modules/ not found")
        .filter_map(|e| {
            let entry = e.ok()?;
            if !entry.file_type().ok()?.is_dir() {
                return None;
            }
            let name = entry.file_name().into_string().ok()?;
            let mod_file = mods_dir.join(&name).join("mod.rs");
            let plugin_toml = mods_dir.join(&name).join("plugin.toml");
            if mod_file.exists() && !plugin_toml.exists() {
                Some(name)
            } else {
                None
            }
        })
        .collect();

    // Sort: priority order first, then alphabetical for the rest.
    modules.sort_by_key(|name| {
        let pri = PRIORITY.iter().position(|p| *p == name.as_str());
        (pri.map(|i| i as u32).unwrap_or(u32::MAX), name.clone())
    });

    // snake_case → PascalCase + "Module"
    let to_struct = |s: &str| -> String {
        s.split('_')
            .map(|w| {
                let mut c = w.chars();
                match c.next() {
                    None => String::new(),
                    Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
                }
            })
            .collect::<String>()
            + "Module"
    };

    let mut out = String::from(
        "// AUTO-GENERATED by build.rs — do not edit by hand.\n\
         // To add a module: create src/modules/my_name/mod.rs + add `pub mod my_name;` to modules/mod.rs.\n\n\
         use crate::modules::CadModule;\n\n\
         /// Returns one boxed instance of every registered CAD module.\n\
         /// Called once at startup by `Ribbon::new()`.\n\
         pub fn all_modules() -> Vec<Box<dyn CadModule>> {\n\
         \tvec![\n",
    );

    for module in &modules {
        let s = to_struct(module);
        out.push_str(&format!("\t\tBox::new(super::{module}::{s}),\n"));
    }

    out.push_str("\t]\n}\n");

    let out_path = mods_dir.join("registry.rs");
    let current = fs::read_to_string(&out_path).unwrap_or_default();
    if current != out {
        fs::write(&out_path, &out).expect("failed to write registry.rs");
    }
}
