//! Build script for ggen workspace
//!
//! Auto-discovers templates and core ontologies at build time and generates Rust code for
//! registration. This eliminates manual maintenance waste and ensures all resources are
//! always accessible.
//!
//! **Features**:
//! - Automatic discovery of all templates (335+)
//! - Compile-time embedding of core ontologies (12 W3C standards)
//! - Type-safe resource access
//! - Zero-cost abstraction (compile-time code generation)

use std::env;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;

fn main() {
    println!("cargo:rerun-if-changed=templates/");
    println!("cargo:rerun-if-changed=ontologies/core/");

    // Discover all templates
    let templates = discover_templates().unwrap_or_else(|e| {
        eprintln!("Warning: Failed to discover templates: {e}");
        Vec::new()
    });

    println!("cargo:warning=Discovered {} templates", templates.len());

    // Discover and embed core ontologies
    let ontologies = discover_core_ontologies().unwrap_or_else(|e| {
        eprintln!("Warning: Failed to discover core ontologies: {e}");
        Vec::new()
    });

    println!(
        "cargo:warning=Discovered {} core ontologies",
        ontologies.len()
    );

    // Generate Rust code for template registry
    generate_template_registry(&templates).unwrap_or_else(|e| {
        eprintln!("Warning: Failed to generate template registry: {e}");
    });

    // Generate Rust code for ontology bundle
    generate_ontology_bundle(&ontologies).unwrap_or_else(|e| {
        eprintln!("Warning: Failed to generate ontology bundle: {e}");
    });
}

/// Discovered template information
#[derive(Debug, Clone)]
struct TemplateInfo {
    /// Relative path from templates/ directory
    path: String,
    /// Template name (derived from path)
    name: String,
    /// Category (directory name)
    category: String,
}

/// Discover all .tmpl files in templates/ directory
fn discover_templates() -> Result<Vec<TemplateInfo>, Box<dyn std::error::Error>> {
    let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
    let templates_dir = Path::new(&manifest_dir).join("templates");

    if !templates_dir.exists() {
        return Ok(Vec::new());
    }

    let mut templates = Vec::new();
    discover_templates_recursive(&templates_dir, &templates_dir, &mut templates)?;

    // Sort for deterministic output
    templates.sort_by(|a, b| a.path.cmp(&b.path));

    Ok(templates)
}

/// Recursively discover templates in directory
fn discover_templates_recursive(
    base_dir: &Path, current_dir: &Path, templates: &mut Vec<TemplateInfo>,
) -> Result<(), Box<dyn std::error::Error>> {
    if !current_dir.is_dir() {
        return Ok(());
    }

    for entry in fs::read_dir(current_dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_dir() {
            discover_templates_recursive(base_dir, &path, templates)?;
        } else if let Some(ext) = path.extension() {
            if ext == "tmpl" {
                let relative_path = path
                    .strip_prefix(base_dir)
                    .map_err(|e| format!("Failed to strip prefix: {e}"))?
                    .to_string_lossy()
                    .to_string();

                // Derive category from directory structure
                let category = path
                    .parent()
                    .and_then(|p| p.strip_prefix(base_dir).ok())
                    .and_then(|p| p.components().next())
                    .map(|c| c.as_os_str().to_string_lossy().to_string())
                    .unwrap_or_else(|| "uncategorized".to_string());

                // Derive name from file name
                let name = path
                    .file_stem()
                    .map(|s| s.to_string_lossy().to_string())
                    .unwrap_or_else(|| "unnamed".to_string());

                templates.push(TemplateInfo {
                    path: relative_path,
                    name,
                    category,
                });
            }
        }
    }

    Ok(())
}

/// Generate Rust code for template registry
fn generate_template_registry(
    templates: &[TemplateInfo],
) -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let dest_path = Path::new(&out_dir).join("templates.rs");
    let mut f = File::create(&dest_path)?;

    // Generate file header
    writeln!(f, "// Auto-generated by build.rs - DO NOT EDIT")?;
    writeln!(f, "// Generated from {} templates", templates.len())?;
    writeln!(f)?;
    writeln!(f, "/// Template metadata discovered at build time")?;
    writeln!(f, "#[derive(Debug, Clone)]")?;
    writeln!(f, "pub struct TemplateMetadata {{")?;
    writeln!(f, "    /// Relative path from templates/ directory")?;
    writeln!(f, "    pub path: &'static str,")?;
    writeln!(f, "    /// Template name")?;
    writeln!(f, "    pub name: &'static str,")?;
    writeln!(f, "    /// Template category")?;
    writeln!(f, "    pub category: &'static str,")?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate template array
    writeln!(
        f,
        "/// All discovered templates (count: {})",
        templates.len()
    )?;
    writeln!(f, "pub static TEMPLATES: &[TemplateMetadata] = &[")?;

    for tmpl in templates {
        writeln!(f, "    TemplateMetadata {{")?;
        writeln!(f, "        path: \"{}\",", escape_string(&tmpl.path))?;
        writeln!(f, "        name: \"{}\",", escape_string(&tmpl.name))?;
        writeln!(
            f,
            "        category: \"{}\",",
            escape_string(&tmpl.category)
        )?;
        writeln!(f, "    }},")?;
    }

    writeln!(f, "];")?;
    writeln!(f)?;

    // Generate category index
    writeln!(f, "/// Get templates by category")?;
    writeln!(
        f,
        "pub fn templates_by_category(category: &str) -> Vec<&'static TemplateMetadata> {{"
    )?;
    writeln!(
        f,
        "    TEMPLATES.iter().filter(|t| t.category == category).collect()"
    )?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate name lookup
    writeln!(f, "/// Find template by name")?;
    writeln!(
        f,
        "pub fn find_template(name: &str) -> Option<&'static TemplateMetadata> {{"
    )?;
    writeln!(f, "    TEMPLATES.iter().find(|t| t.name == name)")?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate statistics
    writeln!(f, "/// Template statistics")?;
    writeln!(f, "pub mod stats {{")?;
    writeln!(
        f,
        "    pub const TOTAL_TEMPLATES: usize = {};",
        templates.len()
    )?;

    // Count templates by category
    let mut categories: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
    for tmpl in templates {
        *categories.entry(tmpl.category.clone()).or_insert(0) += 1;
    }

    writeln!(f, "    pub const CATEGORIES: &[(&str, usize)] = &[")?;
    for (category, count) in categories.iter() {
        writeln!(f, "        (\"{}\", {}),", escape_string(category), count)?;
    }
    writeln!(f, "    ];")?;
    writeln!(f, "}}")?;

    Ok(())
}

/// Escape special characters in strings for Rust code generation
fn escape_string(s: &str) -> String {
    s.replace('\\', "\\\\")
        .replace('\"', "\\\"")
        .replace('\n', "\\n")
        .replace('\r', "\\r")
        .replace('\t', "\\t")
}

/// Core ontology information
#[derive(Debug, Clone)]
struct OntologyInfo {
    /// File name (without extension)
    name: String,
    /// Relative path from ontologies/core/
    path: String,
    /// Namespace URI for this ontology
    namespace: String,
    /// File size in bytes
    size: u64,
}

/// Discover all ontology files in ontologies/core/ directory
fn discover_core_ontologies() -> Result<Vec<OntologyInfo>, Box<dyn std::error::Error>> {
    let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
    let ontologies_dir = Path::new(&manifest_dir).join("ontologies").join("core");

    if !ontologies_dir.exists() {
        return Ok(Vec::new());
    }

    let mut ontologies = Vec::new();

    for entry in fs::read_dir(&ontologies_dir)? {
        let entry = entry?;
        let path = entry.path();

        if path.is_file() {
            if let Some(ext) = path.extension() {
                if ext == "ttl" || ext == "rdf" {
                    let file_name = path
                        .file_stem()
                        .map(|s| s.to_string_lossy().to_string())
                        .unwrap_or_else(|| "unknown".to_string());

                    let relative_path = path
                        .strip_prefix(&ontologies_dir)
                        .map_err(|e| format!("Failed to strip prefix: {e}"))?
                        .to_string_lossy()
                        .to_string();

                    let metadata = fs::metadata(&path)?;
                    let size = metadata.len();

                    // Map file names to their namespace URIs
                    let namespace = match file_name.as_str() {
                        "rdf-syntax-ns" => {
                            "http://www.w3.org/1999/02/22-rdf-syntax-ns#".to_string()
                        }
                        "rdf-schema" => "http://www.w3.org/2000/01/rdf-schema#".to_string(),
                        "owl" | "owl2" => "http://www.w3.org/2002/07/owl#".to_string(),
                        "skos" => "http://www.w3.org/2004/02/skos/core#".to_string(),
                        "skos-xl" => "http://www.w3.org/2008/05/skos-xl#".to_string(),
                        "dcterms" => "http://purl.org/dc/terms/".to_string(),
                        "dcat" => "http://www.w3.org/ns/dcat#".to_string(),
                        "prov-o" => "http://www.w3.org/ns/prov#".to_string(),
                        "shacl" => "http://www.w3.org/ns/shacl#".to_string(),
                        "xmlschema-datatypes" => "http://www.w3.org/2001/XMLSchema#".to_string(),
                        "ocel2" => "https://ocelprov.org/ontology#".to_string(),
                        _ => format!("http://unknown.org/ontology/{file_name}#"),
                    };

                    ontologies.push(OntologyInfo {
                        name: file_name,
                        path: relative_path,
                        namespace,
                        size,
                    });
                }
            }
        }
    }

    // Sort for deterministic output
    ontologies.sort_by(|a, b| a.path.cmp(&b.path));

    Ok(ontologies)
}

/// Generate Rust code for ontology bundle (embedded at compile time)
fn generate_ontology_bundle(ontologies: &[OntologyInfo]) -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let dest_path = Path::new(&out_dir).join("ontologies.rs");
    let mut f = File::create(&dest_path)?;

    writeln!(f, "// Auto-generated by build.rs - DO NOT EDIT")?;
    writeln!(f, "// Generated from {} core ontologies", ontologies.len())?;
    writeln!(f)?;
    writeln!(f, "/// Information about an embedded core ontology")?;
    writeln!(f, "#[derive(Debug, Clone, Copy)]")?;
    writeln!(f, "pub struct OntologyMetadata {{")?;
    writeln!(f, "    /// Ontology name (filename without extension)")?;
    writeln!(f, "    pub name: &'static str,")?;
    writeln!(f, "    /// Namespace URI for this ontology")?;
    writeln!(f, "    pub namespace: &'static str,")?;
    writeln!(f, "    /// Embedded ontology content (Turtle or RDF/XML)")?;
    writeln!(f, "    pub content: &'static [u8],")?;
    writeln!(f, "    /// Original file size")?;
    writeln!(f, "    pub size: usize,")?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate include_bytes! declarations
    writeln!(f, "// Embedded ontology content")?;
    for ontology in ontologies {
        let const_name = format!(
            "ONTOLOGY_{}",
            ontology.name.to_uppercase().replace('-', "_")
        );
        let safe_path = ontology.path.replace('\\', "/");
        writeln!(
            f,
            "const {}: &[u8] = include_bytes!(\"../../ontologies/core/{}\");",
            const_name, safe_path
        )?;
    }
    writeln!(f)?;

    // Generate metadata array
    writeln!(f, "/// All embedded core ontologies")?;
    writeln!(f, "pub static CORE_ONTOLOGIES: &[OntologyMetadata] = &[")?;

    for ontology in ontologies {
        let const_name = format!(
            "ONTOLOGY_{}",
            ontology.name.to_uppercase().replace('-', "_")
        );
        writeln!(f, "    OntologyMetadata {{")?;
        writeln!(f, "        name: \"{}\",", escape_string(&ontology.name))?;
        writeln!(
            f,
            "        namespace: \"{}\",",
            escape_string(&ontology.namespace)
        )?;
        writeln!(f, "        content: {},", const_name)?;
        writeln!(f, "        size: {},", ontology.size)?;
        writeln!(f, "    }},")?;
    }

    writeln!(f, "];")?;
    writeln!(f)?;

    // Generate namespace lookup function
    writeln!(f, "/// Look up an ontology by namespace URI")?;
    writeln!(
        f,
        "pub fn by_namespace(uri: &str) -> Option<&'static OntologyMetadata> {{"
    )?;
    writeln!(f, "    CORE_ONTOLOGIES.iter().find(|o| o.namespace == uri)")?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate namespace lookup by partial URI
    writeln!(f, "/// Look up an ontology by name")?;
    writeln!(
        f,
        "pub fn by_name(name: &str) -> Option<&'static OntologyMetadata> {{"
    )?;
    writeln!(f, "    CORE_ONTOLOGIES.iter().find(|o| o.name == name)")?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate availability function
    writeln!(f, "/// Get list of all available core ontologies")?;
    writeln!(
        f,
        "pub fn available() -> Vec<(&'static str, &'static str)> {{"
    )?;
    writeln!(
        f,
        "    CORE_ONTOLOGIES.iter().map(|o| (o.name, o.namespace)).collect()"
    )?;
    writeln!(f, "}}")?;
    writeln!(f)?;

    // Generate statistics
    let total_size: u64 = ontologies.iter().map(|o| o.size).sum();
    writeln!(f, "/// Statistics about embedded ontologies")?;
    writeln!(f, "pub mod stats {{")?;
    writeln!(
        f,
        "    pub const TOTAL_ONTOLOGIES: usize = {};",
        ontologies.len()
    )?;
    writeln!(f, "    pub const TOTAL_SIZE_BYTES: usize = {};", total_size)?;
    writeln!(f, "}}")?;

    Ok(())
}
