use handlebars::Handlebars;
use serde_json::json;
use rustc_hash::FxHashMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

#[derive(serde::Deserialize, Debug)]
struct ScriptMetadata {
    name: String,
    script_type: String,
    has_implicit_a: bool,
}

#[derive(serde::Deserialize, Debug)]
struct ScriptMappings {
    vowels: Option<FxHashMap<String, String>>,
    consonants: Option<FxHashMap<String, String>>,
    vowel_signs: Option<FxHashMap<String, String>>,
    marks: Option<FxHashMap<String, String>>,
    digits: Option<FxHashMap<String, String>>,
    sanskrit_extensions: Option<FxHashMap<String, String>>,
    special: Option<FxHashMap<String, String>>,
}

#[derive(serde::Deserialize, Debug)]
struct CodegenConfig {
    processor_type: String,
}

#[derive(serde::Deserialize, Debug)]
struct ScriptSchema {
    metadata: ScriptMetadata,
    target: Option<String>, // "iso15919" for Roman, "devanagari" for Indic (default)
    mappings: ScriptMappings,
    codegen: Option<CodegenConfig>,
}

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

    if let Err(e) = generate_schema_based_converters() {
        println!("cargo:warning=Failed to generate schema-based converters: {e}");
    }
}

fn generate_schema_based_converters() -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = PathBuf::from(env::var("OUT_DIR")?);
    let schemas_dir = Path::new("schemas");

    // Initialize Handlebars template engine
    let mut handlebars = Handlebars::new();
    handlebars.register_template_file("roman_converter", "templates/roman_converter.hbs")?;
    handlebars.register_template_file(
        "roman_to_devanagari_direct",
        "templates/roman_to_devanagari_direct.hbs",
    )?;
    handlebars.register_template_file(
        "indic_standard_converter",
        "templates/indic_standard_converter.hbs",
    )?;
    handlebars.register_template_file(
        "indic_extended_converter",
        "templates/indic_extended_converter.hbs",
    )?;

    // Register helper functions for templates
    handlebars.register_helper("uppercase", Box::new(uppercase_helper));
    handlebars.register_helper("lowercase", Box::new(lowercase_helper));
    handlebars.register_helper("capitalize", Box::new(capitalize_helper));
    handlebars.register_helper("escape", Box::new(escape_helper));

    let mut generated_code = String::new();
    let mut converter_registrations = Vec::new();

    // Add header
    generated_code.push_str(
        r#"
// Auto-generated converters from TOML/YAML schemas
// DO NOT EDIT - Generated by build.rs at compile time

use once_cell::sync::Lazy;
use crate::modules::script_converter::processors::RomanScriptProcessor;
use crate::modules::hub::{HubFormat, HubTrait};

"#,
    );

    // Process YAML schemas
    if schemas_dir.exists() {
        for entry in fs::read_dir(schemas_dir)? {
            let entry = entry?;
            let path = entry.path();

            if path.extension().and_then(|s| s.to_str()) == Some("yaml") {
                println!("cargo:rerun-if-changed={}", path.display());

                let content = fs::read_to_string(&path)?;
                let schema: ScriptSchema = serde_yaml::from_str(&content).map_err(|e| {
                    format!("Failed to parse YAML schema {}: {}", path.display(), e)
                })?;

                // Skip Devanagari as it's the hub script and handled directly
                if schema.metadata.name.to_lowercase() == "devanagari" {
                    continue;
                }

                let converter_code =
                    generate_converter_from_schema(&handlebars, &schema).map_err(|e| {
                        format!(
                            "Failed to generate converter for {}: {}",
                            schema.metadata.name, e
                        )
                    })?;
                generated_code.push_str(&converter_code);

                converter_registrations.push(format!(
                    "{}Converter",
                    capitalize_first(&schema.metadata.name)
                ));

                // For Roman scripts targeting ISO-15919, also generate direct Roman → Devanagari converter
                if schema.metadata.script_type == "roman" && schema.target.as_deref() == Some("iso15919") {
                    let devanagari_converter_code = generate_roman_to_devanagari_converter(
                        &handlebars, 
                        &schema
                    ).map_err(|e| {
                        format!(
                            "Failed to generate Roman→Devanagari converter for {}: {}",
                            schema.metadata.name, e
                        )
                    })?;
                    generated_code.push_str(&devanagari_converter_code);

                    converter_registrations.push(format!(
                        "{}DevanagariConverter",
                        capitalize_first(&schema.metadata.name)
                    ));
                }
            }
        }
    }

    // Add registration function
    generated_code.push_str(&format!(r#"
/// Register all schema-generated converters
pub fn register_schema_generated_converters(registry: &mut crate::modules::script_converter::ScriptConverterRegistry) {{
{}
}}
"#, 
        converter_registrations.iter()
            .map(|name| format!("    registry.register_converter(Box::new({name}::new()));"))
            .collect::<Vec<_>>()
            .join("\n")
    ));

    // Write generated code
    fs::write(out_dir.join("schema_generated.rs"), generated_code)?;

    Ok(())
}

fn generate_converter_from_schema(
    handlebars: &Handlebars,
    schema: &ScriptSchema,
) -> Result<String, Box<dyn std::error::Error>> {
    let script_name = &schema.metadata.name;
    let struct_name = format!("{}Converter", capitalize_first(script_name));

    // Determine processor type based on target or script_type
    let processor_type = schema
        .codegen
        .as_ref()
        .map(|c| c.processor_type.as_str())
        .unwrap_or_else(|| {
            // Use target field to determine processor type
            match schema.target.as_deref() {
                Some("iso15919") => "roman",
                Some("devanagari") => "indic_standard",
                None => {
                    // Fallback to script_type for backward compatibility
                    if schema.metadata.script_type == "roman" {
                        "roman"
                    } else {
                        "indic_standard"
                    }
                }
                _ => "indic_standard",
            }
        });

    // Combine all mappings
    let mut all_mappings = FxHashMap::default();
    if let Some(ref vowels) = schema.mappings.vowels {
        all_mappings.extend(vowels.clone());
    }
    if let Some(ref consonants) = schema.mappings.consonants {
        all_mappings.extend(consonants.clone());
    }
    if let Some(ref vowel_signs) = schema.mappings.vowel_signs {
        all_mappings.extend(vowel_signs.clone());
    }
    if let Some(ref marks) = schema.mappings.marks {
        all_mappings.extend(marks.clone());
    }
    if let Some(ref digits) = schema.mappings.digits {
        all_mappings.extend(digits.clone());
    }
    if let Some(ref sanskrit_extensions) = schema.mappings.sanskrit_extensions {
        all_mappings.extend(sanskrit_extensions.clone());
    }
    if let Some(ref special) = schema.mappings.special {
        all_mappings.extend(special.clone());
    }

    match processor_type {
        "roman" => generate_roman_converter_with_template(
            handlebars,
            &struct_name,
            script_name,
            &all_mappings,
            &schema.metadata,
        ),
        "indic_standard" => generate_indic_converter_with_template(
            handlebars,
            &struct_name,
            script_name,
            &all_mappings,
            &schema.metadata,
        ),
        "indic_extended" => generate_extended_indic_converter_with_template(
            handlebars,
            &struct_name,
            script_name,
            &all_mappings,
            &schema.metadata,
        ),
        _ => Err(format!("Unknown processor type: {processor_type}").into()),
    }
}

fn capitalize_first(s: &str) -> String {
    // Convert kebab-case and snake_case to PascalCase
    s.split(&['-', '_'][..])
        .map(|part| {
            let mut chars = part.chars();
            match chars.next() {
                None => String::new(),
                Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
            }
        })
        .collect()
}

fn escape_string(s: &str) -> String {
    s.replace('\\', "\\\\").replace('"', "\\\"")
}

// Template-based converter generators

fn generate_roman_converter_with_template(
    handlebars: &Handlebars,
    struct_name: &str,
    script_name: &str,
    mappings: &FxHashMap<String, String>,
    metadata: &ScriptMetadata,
) -> Result<String, Box<dyn std::error::Error>> {
    // Sort by length (longest first) for proper matching
    let mut sorted_mappings: Vec<_> = mappings.iter().collect();
    sorted_mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len()));

    // Prepare reverse priority mappings for template
    let reverse_priority_mappings: FxHashMap<&str, &str> = sorted_mappings
        .iter()
        .filter(|(_from, to)| to.len() > 1) // Multi-character sequences get priority
        .map(|(from, to)| (to.as_str(), from.as_str()))
        .collect();

    // Convert to JSON for template
    let mappings_json: FxHashMap<&str, &str> = mappings
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();

    // Add script-specific aliases
    let aliases = match script_name {
        "harvard_kyoto" => vec![
            ("hk_to_iso", "harvard_kyoto_to_iso"),
            ("iso_to_hk", "iso_to_harvard_kyoto"),
        ],
        _ => vec![],
    };

    let template_data = json!({
        "struct_name": struct_name,
        "script_name": script_name,
        "has_implicit_a": metadata.has_implicit_a,
        "mappings": mappings_json,
        "reverse_priority_mappings": reverse_priority_mappings,
        "aliases": aliases
    });

    let code = handlebars.render("roman_converter", &template_data)?;
    Ok(code)
}

// Handlebars helper functions
fn uppercase_helper(
    h: &handlebars::Helper,
    _: &handlebars::Handlebars,
    _: &handlebars::Context,
    _rc: &mut handlebars::RenderContext,
    out: &mut dyn handlebars::Output,
) -> handlebars::HelperResult {
    let param = h.param(0).unwrap().value().as_str().unwrap();
    out.write(&param.to_uppercase())?;
    Ok(())
}

fn lowercase_helper(
    h: &handlebars::Helper,
    _: &handlebars::Handlebars,
    _: &handlebars::Context,
    _rc: &mut handlebars::RenderContext,
    out: &mut dyn handlebars::Output,
) -> handlebars::HelperResult {
    let param = h.param(0).unwrap().value().as_str().unwrap();
    out.write(&param.to_lowercase())?;
    Ok(())
}

fn capitalize_helper(
    h: &handlebars::Helper,
    _: &handlebars::Handlebars,
    _: &handlebars::Context,
    _rc: &mut handlebars::RenderContext,
    out: &mut dyn handlebars::Output,
) -> handlebars::HelperResult {
    let param = h.param(0).unwrap().value().as_str().unwrap();
    out.write(&capitalize_first(param))?;
    Ok(())
}

fn escape_helper(
    h: &handlebars::Helper,
    _: &handlebars::Handlebars,
    _: &handlebars::Context,
    _rc: &mut handlebars::RenderContext,
    out: &mut dyn handlebars::Output,
) -> handlebars::HelperResult {
    let param = h.param(0).unwrap().value().as_str().unwrap();
    out.write(&escape_string(param))?;
    Ok(())
}

fn generate_indic_converter_with_template(
    handlebars: &Handlebars,
    struct_name: &str,
    script_name: &str,
    mappings: &FxHashMap<String, String>,
    metadata: &ScriptMetadata,
) -> Result<String, Box<dyn std::error::Error>> {
    // Filter to only single-character mappings for optimized HashMap<char, char>
    let char_mappings: FxHashMap<&str, &str> = mappings
        .iter()
        .filter(|(from, to)| from.chars().count() == 1 && to.chars().count() == 1)
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();

    let template_data = json!({
        "struct_name": struct_name,
        "script_name": script_name,
        "has_implicit_a": metadata.has_implicit_a,
        "char_mappings": char_mappings
    });

    let code = handlebars.render("indic_standard_converter", &template_data)?;
    Ok(code)
}

fn generate_extended_indic_converter_with_template(
    handlebars: &Handlebars,
    struct_name: &str,
    script_name: &str,
    mappings: &FxHashMap<String, String>,
    metadata: &ScriptMetadata,
) -> Result<String, Box<dyn std::error::Error>> {
    // Use all mappings as strings for complex script support
    let string_mappings: FxHashMap<&str, &str> = mappings
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();

    let template_data = json!({
        "struct_name": struct_name,
        "script_name": script_name,
        "has_implicit_a": metadata.has_implicit_a,
        "string_mappings": string_mappings
    });

    let code = handlebars.render("indic_extended_converter", &template_data)?;
    Ok(code)
}

fn generate_roman_to_devanagari_converter(
    handlebars: &Handlebars,
    schema: &ScriptSchema,
) -> Result<String, Box<dyn std::error::Error>> {
    let script_name = &schema.metadata.name;
    let struct_name = format!("{}DevanagariConverter", capitalize_first(script_name));

    // Get ISO-15919 to Devanagari mappings from hub (at compile time)
    let iso_to_deva_mappings = get_iso_to_devanagari_mappings();

    // Compose Roman → ISO-15919 → Devanagari mappings at COMPILE TIME
    let mut roman_to_deva_mappings = FxHashMap::default();
    
    // Process all mapping categories from the schema
    let all_roman_mappings = [
        &schema.mappings.vowels,
        &schema.mappings.consonants,
        &schema.mappings.marks,
        &schema.mappings.digits,
        &schema.mappings.sanskrit_extensions,
        &schema.mappings.special,
    ];

    for mapping_category in all_roman_mappings.iter() {
        if let Some(mappings) = mapping_category {
            for (roman_key, iso_value) in mappings.iter() {
                // Direct mapping for all keys
                if let Some(deva_value) = iso_to_deva_mappings.get(iso_value) {
                    roman_to_deva_mappings.insert(roman_key.clone(), deva_value.clone());
                }
                
                // For consonants, also try with 'a' suffix (for inherent vowel)
                let iso_with_a = format!("{}a", iso_value);
                if let Some(deva_value) = iso_to_deva_mappings.get(&iso_with_a) {
                    let roman_with_a = format!("{}a", roman_key);
                    roman_to_deva_mappings.insert(roman_with_a, deva_value.clone());
                }
            }
        }
    }
    
    // Add vowel sign mappings by deriving them from hub data
    add_vowel_sign_mappings(&mut roman_to_deva_mappings, &iso_to_deva_mappings, &schema.mappings.vowels);

    // Sort by length (longest first) for proper matching
    let mut sorted_mappings: Vec<_> = roman_to_deva_mappings.iter().collect();
    sorted_mappings.sort_by(|a, b| b.0.len().cmp(&a.0.len()));

    // Convert to template format - use the original String keys
    let mappings_for_template = &roman_to_deva_mappings;

    let template_data = json!({
        "struct_name": struct_name,
        "script_name": format!("{}_devanagari", script_name),
        "original_script": script_name,
        "has_implicit_a": schema.metadata.has_implicit_a,
        "mappings": mappings_for_template,
    });

    let code = handlebars.render("roman_to_devanagari_direct", &template_data)?;
    Ok(code)
}

fn get_iso_to_devanagari_mappings() -> FxHashMap<String, String> {
    // Hardcode the hub mappings since we can't access the crate from build.rs
    // This is still better than before since we're deriving vowel signs dynamically
    let mut iso_to_deva_mappings = FxHashMap::default();
    
    // Core vowels
    iso_to_deva_mappings.insert("a".to_string(), "अ".to_string());
    iso_to_deva_mappings.insert("ā".to_string(), "आ".to_string());
    iso_to_deva_mappings.insert("i".to_string(), "इ".to_string());
    iso_to_deva_mappings.insert("ī".to_string(), "ई".to_string());
    iso_to_deva_mappings.insert("u".to_string(), "उ".to_string());
    iso_to_deva_mappings.insert("ū".to_string(), "ऊ".to_string());
    iso_to_deva_mappings.insert("r̥".to_string(), "ऋ".to_string());
    iso_to_deva_mappings.insert("r̥̄".to_string(), "ॠ".to_string());
    iso_to_deva_mappings.insert("l̥".to_string(), "ऌ".to_string());
    iso_to_deva_mappings.insert("l̥̄".to_string(), "ॡ".to_string());
    iso_to_deva_mappings.insert("e".to_string(), "ए".to_string());
    iso_to_deva_mappings.insert("ai".to_string(), "ऐ".to_string());
    iso_to_deva_mappings.insert("o".to_string(), "ओ".to_string());
    iso_to_deva_mappings.insert("au".to_string(), "औ".to_string());

    // Consonants with inherent 'a' vowel
    iso_to_deva_mappings.insert("ka".to_string(), "क".to_string());
    iso_to_deva_mappings.insert("kha".to_string(), "ख".to_string());
    iso_to_deva_mappings.insert("ga".to_string(), "ग".to_string());
    iso_to_deva_mappings.insert("gha".to_string(), "घ".to_string());
    iso_to_deva_mappings.insert("ṅa".to_string(), "ङ".to_string());
    iso_to_deva_mappings.insert("ca".to_string(), "च".to_string());
    iso_to_deva_mappings.insert("cha".to_string(), "छ".to_string());
    iso_to_deva_mappings.insert("ja".to_string(), "ज".to_string());
    iso_to_deva_mappings.insert("jha".to_string(), "झ".to_string());
    iso_to_deva_mappings.insert("ña".to_string(), "ञ".to_string());
    iso_to_deva_mappings.insert("ṭa".to_string(), "ट".to_string());
    iso_to_deva_mappings.insert("ṭha".to_string(), "ठ".to_string());
    iso_to_deva_mappings.insert("ḍa".to_string(), "ड".to_string());
    iso_to_deva_mappings.insert("ḍha".to_string(), "ढ".to_string());
    iso_to_deva_mappings.insert("ṇa".to_string(), "ण".to_string());
    iso_to_deva_mappings.insert("ta".to_string(), "त".to_string());
    iso_to_deva_mappings.insert("tha".to_string(), "थ".to_string());
    iso_to_deva_mappings.insert("da".to_string(), "द".to_string());
    iso_to_deva_mappings.insert("dha".to_string(), "ध".to_string());
    iso_to_deva_mappings.insert("na".to_string(), "न".to_string());
    iso_to_deva_mappings.insert("pa".to_string(), "प".to_string());
    iso_to_deva_mappings.insert("pha".to_string(), "फ".to_string());
    iso_to_deva_mappings.insert("ba".to_string(), "ब".to_string());
    iso_to_deva_mappings.insert("bha".to_string(), "भ".to_string());
    iso_to_deva_mappings.insert("ma".to_string(), "म".to_string());
    iso_to_deva_mappings.insert("ya".to_string(), "य".to_string());
    iso_to_deva_mappings.insert("ra".to_string(), "र".to_string());
    iso_to_deva_mappings.insert("la".to_string(), "ल".to_string());
    iso_to_deva_mappings.insert("va".to_string(), "व".to_string());
    iso_to_deva_mappings.insert("śa".to_string(), "श".to_string());
    iso_to_deva_mappings.insert("ṣa".to_string(), "ष".to_string());
    iso_to_deva_mappings.insert("sa".to_string(), "स".to_string());
    iso_to_deva_mappings.insert("ha".to_string(), "ह".to_string());

    // Consonants with other vowels for vowel sign extraction
    iso_to_deva_mappings.insert("kā".to_string(), "का".to_string());
    iso_to_deva_mappings.insert("ki".to_string(), "कि".to_string());
    iso_to_deva_mappings.insert("kī".to_string(), "की".to_string());
    iso_to_deva_mappings.insert("ku".to_string(), "कु".to_string());
    iso_to_deva_mappings.insert("kū".to_string(), "कू".to_string());
    iso_to_deva_mappings.insert("kr̥".to_string(), "कृ".to_string());
    iso_to_deva_mappings.insert("kr̥̄".to_string(), "कॄ".to_string());
    iso_to_deva_mappings.insert("kl̥".to_string(), "कॢ".to_string());
    iso_to_deva_mappings.insert("kl̥̄".to_string(), "कॣ".to_string());
    iso_to_deva_mappings.insert("ke".to_string(), "के".to_string());
    iso_to_deva_mappings.insert("kai".to_string(), "कै".to_string());
    iso_to_deva_mappings.insert("ko".to_string(), "को".to_string());
    iso_to_deva_mappings.insert("kau".to_string(), "कौ".to_string());

    // Marks
    iso_to_deva_mappings.insert("ṁ".to_string(), "ं".to_string());
    iso_to_deva_mappings.insert("ḥ".to_string(), "ः".to_string());
    iso_to_deva_mappings.insert("m̐".to_string(), "ँ".to_string());
    iso_to_deva_mappings.insert("'".to_string(), "ऽ".to_string());

    // Digits
    iso_to_deva_mappings.insert("0".to_string(), "०".to_string());
    iso_to_deva_mappings.insert("1".to_string(), "१".to_string());
    iso_to_deva_mappings.insert("2".to_string(), "२".to_string());
    iso_to_deva_mappings.insert("3".to_string(), "३".to_string());
    iso_to_deva_mappings.insert("4".to_string(), "४".to_string());
    iso_to_deva_mappings.insert("5".to_string(), "५".to_string());
    iso_to_deva_mappings.insert("6".to_string(), "६".to_string());
    iso_to_deva_mappings.insert("7".to_string(), "७".to_string());
    iso_to_deva_mappings.insert("8".to_string(), "८".to_string());
    iso_to_deva_mappings.insert("9".to_string(), "९".to_string());

    // Special
    iso_to_deva_mappings.insert("ḷa".to_string(), "ळ".to_string());
    iso_to_deva_mappings.insert("।".to_string(), "।".to_string());
    iso_to_deva_mappings.insert("॥".to_string(), "॥".to_string());
    iso_to_deva_mappings.insert("kṣa".to_string(), "क्ष".to_string());
    iso_to_deva_mappings.insert("jña".to_string(), "ज्ञ".to_string());
    
    // Nukta consonants (for extended characters) - using precomposed forms
    iso_to_deva_mappings.insert("qa".to_string(), "\u{0958}".to_string()); // क़ precomposed
    iso_to_deva_mappings.insert("ḵẖa".to_string(), "\u{0959}".to_string()); // ख़ precomposed
    iso_to_deva_mappings.insert("ġa".to_string(), "\u{095A}".to_string()); // ग़ precomposed
    iso_to_deva_mappings.insert("za".to_string(), "\u{095B}".to_string()); // ज़ precomposed
    iso_to_deva_mappings.insert("ṛa".to_string(), "\u{095C}".to_string()); // ड़ precomposed
    iso_to_deva_mappings.insert("ṛha".to_string(), "\u{095D}".to_string()); // ढ़ precomposed
    iso_to_deva_mappings.insert("fa".to_string(), "\u{095E}".to_string()); // फ़ precomposed
    iso_to_deva_mappings.insert("ẏa".to_string(), "\u{095F}".to_string()); // य़ precomposed

    iso_to_deva_mappings
}

fn add_vowel_sign_mappings(
    roman_to_deva_mappings: &mut FxHashMap<String, String>,
    iso_to_deva_mappings: &FxHashMap<String, String>, 
    vowel_mappings: &Option<FxHashMap<String, String>>
) {
    if let Some(vowels) = vowel_mappings {
        for (roman_vowel, iso_vowel) in vowels.iter() {
            // Get the vowel sign by testing with a sample consonant
            if let (Some(ka_vowel), Some(ka_base)) = (
                iso_to_deva_mappings.get(&format!("k{}", iso_vowel)),
                iso_to_deva_mappings.get("ka")
            ) {
                // Extract vowel sign by comparing ka + vowel vs ka
                if ka_vowel != ka_base && ka_vowel.starts_with(ka_base) {
                    let vowel_sign = &ka_vowel[ka_base.len()..];
                    if !vowel_sign.is_empty() {
                        roman_to_deva_mappings.insert(
                            format!("__vowel_sign_{}", roman_vowel),
                            vowel_sign.to_string()
                        );
                    }
                }
            }
        }
    }
}
