//! Type-safe schema code generation.
//!
//! Generates Rust code from schema.qail for compile-time type safety.
//!
//! # Usage from build.rs
//! ```ignore
//! qail_core::codegen::generate_to_file("schema.qail", "src/generated/schema.rs")?;
//! ```
//!
//! # Generated code example
//! ```ignore
//! pub mod users {
//!     use qail_core::typed::{Table, TypedColumn};
//!     
//!     pub struct Users;
//!     impl Table for Users { fn table_name() -> &'static str { "users" } }
//!     
//!     pub fn id() -> TypedColumn<uuid::Uuid> { TypedColumn::new("users", "id") }
//!     pub fn age() -> TypedColumn<i64> { TypedColumn::new("users", "age") }
//! }
//! ```

use crate::build::Schema;
use std::fs;

/// Generate typed Rust code from a schema.qail file and write to output
pub fn generate_to_file(schema_path: &str, output_path: &str) -> Result<(), String> {
    let schema = Schema::parse_file(schema_path)?;
    let code = generate_schema_code(&schema);
    fs::write(output_path, &code)
        .map_err(|e| format!("Failed to write output: {}", e))?;
    Ok(())
}

/// Generate typed Rust code from a schema.qail file
pub fn generate_from_file(schema_path: &str) -> Result<String, String> {
    let schema = Schema::parse_file(schema_path)?;
    Ok(generate_schema_code(&schema))
}

/// Generate Rust code for the schema
pub fn generate_schema_code(schema: &Schema) -> String {
    let mut code = String::new();
    
    // Header
    code.push_str("//! Auto-generated by `qail types`\n");
    code.push_str("//! Do not edit manually.\n\n");
    code.push_str("#![allow(dead_code)]\n\n");
    code.push_str("use qail_core::typed::{Table, TypedColumn};\n\n");
    
    // Generate table modules
    let mut table_names: Vec<_> = schema.tables.keys().collect();
    table_names.sort();
    
    for table_name in &table_names {
        if let Some(table) = schema.tables.get(*table_name) {
            code.push_str(&generate_table_module(table_name, table));
            code.push('\n');
        }
    }
    
    // Generate tables re-export
    code.push_str("/// Re-export all table types\n");
    code.push_str("pub mod tables {\n");
    
    for table_name in &table_names {
        let struct_name = to_pascal_case(table_name);
        code.push_str(&format!(
            "    pub use super::{}::{};\n",
            table_name, struct_name
        ));
    }
    code.push_str("}\n");
    
    code
}

/// Generate a module for a single table
fn generate_table_module(table_name: &str, table: &crate::build::TableSchema) -> String {
    let mut code = String::new();
    let struct_name = to_pascal_case(table_name);
    
    code.push_str(&format!("/// Table: {}\n", table_name));
    code.push_str(&format!("pub mod {} {{\n", table_name));
    code.push_str("    use super::*;\n\n");
    
    // Table struct with Table trait
    code.push_str(&format!("    /// Type-safe reference to `{}`\n", table_name));
    code.push_str("    #[derive(Debug, Clone, Copy, Default)]\n");
    code.push_str(&format!("    pub struct {};\n\n", struct_name));
    
    code.push_str(&format!("    impl Table for {} {{\n", struct_name));
    code.push_str(&format!(
        "        fn table_name() -> &'static str {{ \"{}\" }}\n",
        table_name
    ));
    code.push_str("    }\n\n");
    
    // Implement From<Table> for String to work with Qail::get()
    code.push_str(&format!("    impl From<{}> for String {{\n", struct_name));
    code.push_str(&format!("        fn from(_: {}) -> String {{ \"{}\".to_string() }}\n", struct_name, table_name));
    code.push_str("    }\n\n");
    
    // Typed column functions
    let mut col_names: Vec<_> = table.columns.keys().collect();
    col_names.sort();
    
    for col_name in &col_names {
        if let Some(col_type) = table.columns.get(*col_name) {
            let rust_type = sql_type_to_rust(col_type);
            let fn_name = escape_keyword(col_name);
            code.push_str(&format!(
                "    /// Column `{}` ({})\n",
                col_name, col_type
            ));
            code.push_str(&format!(
                "    pub fn {}() -> TypedColumn<{}> {{ TypedColumn::new(\"{}\", \"{}\") }}\n\n",
                fn_name, rust_type, table_name, col_name
            ));
        }
    }
    
    code.push_str("}\n");
    
    code
}

/// Map SQL types to Rust types
pub fn sql_type_to_rust(sql_type: &str) -> &'static str {
    let upper = sql_type.to_uppercase();
    
    // Integer family
    if upper.contains("BIGINT") || upper.contains("INT8") || upper.contains("BIGSERIAL") {
        return "i64";
    }
    if upper.contains("INT") || upper.contains("SERIAL") {
        return "i64";  // Use i64 for all ints for simplicity
    }
    
    // Float family
    if upper.contains("FLOAT") || upper.contains("DOUBLE") || 
       upper.contains("DECIMAL") || upper.contains("NUMERIC") || upper.contains("REAL") {
        return "f64";
    }
    
    // Boolean
    if upper.contains("BOOL") {
        return "bool";
    }
    
    // UUID
    if upper.contains("UUID") {
        return "uuid::Uuid";
    }
    
    // Text family
    if upper.contains("TEXT") || upper.contains("VARCHAR") || 
       upper.contains("CHAR") || upper.contains("NAME") {
        return "String";
    }
    
    // JSON
    if upper.contains("JSON") {
        return "serde_json::Value";
    }
    
    // Timestamp
    if upper.contains("TIMESTAMP") || upper.contains("DATE") || upper.contains("TIME") {
        return "chrono::DateTime<chrono::Utc>";
    }
    
    // Bytea
    if upper.contains("BYTEA") || upper.contains("BLOB") {
        return "Vec<u8>";
    }
    
    // Default to String for unknown types
    "String"
}

/// Convert snake_case to PascalCase
fn to_pascal_case(s: &str) -> String {
    s.split('_')
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                None => String::new(),
                Some(c) => c.to_uppercase().chain(chars).collect(),
            }
        })
        .collect()
}

/// Escape Rust reserved keywords with r# prefix
fn escape_keyword(name: &str) -> String {
    const KEYWORDS: &[&str] = &[
        "as", "break", "const", "continue", "crate", "else", "enum", "extern",
        "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod",
        "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct",
        "super", "trait", "true", "type", "unsafe", "use", "where", "while",
        "async", "await", "dyn", "abstract", "become", "box", "do", "final",
        "macro", "override", "priv", "try", "typeof", "unsized", "virtual", "yield",
    ];
    
    if KEYWORDS.contains(&name) {
        format!("r#{}", name)
    } else {
        name.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_pascal_case() {
        assert_eq!(to_pascal_case("users"), "Users");
        assert_eq!(to_pascal_case("user_profiles"), "UserProfiles");
    }
    
    #[test]
    fn test_sql_type_mapping() {
        assert_eq!(sql_type_to_rust("INT"), "i64");
        assert_eq!(sql_type_to_rust("TEXT"), "String");
        assert_eq!(sql_type_to_rust("UUID"), "uuid::Uuid");
        assert_eq!(sql_type_to_rust("BOOLEAN"), "bool");
        assert_eq!(sql_type_to_rust("JSONB"), "serde_json::Value");
    }
}
