use std::collections::HashSet;
use std::fs;
use std::process::Command;

// Import shared utilities
include!("src/engine/data_tree_builder.rs");

// Constants for paths
const CALENDARS_DIR: &str = "../data/definitions";
const RESOURCES_DIR: &str = "../data/resources";
const CALENDAR_IDS_FILE: &str = "src/generated/calendar_ids.rs";
const LOCALE_IDS_FILE: &str = "src/generated/locale_ids.rs";

fn generate_constants() {
    println!("cargo:info=Generating constants from JSON files...");

    // Collect calendar information (ID and parent relationships)
    let mut calendar_infos = Vec::new();
    collect_calendar_infos_recursively(CALENDARS_DIR, &mut calendar_infos);

    // Collect locales from JSON "locale" property
    let mut locales = HashSet::new();
    collect_locale_codes_recursively(RESOURCES_DIR, &mut locales);

    // Extract calendar IDs and sort them
    let mut calendar_ids: Vec<String> = calendar_infos.iter().map(|info| info.id.clone()).collect();
    calendar_ids.sort();

    // Convert locales to sorted vector
    let mut locales: Vec<String> = locales.into_iter().collect();
    locales.sort();

    // Build calendar tree
    let calendar_tree = build_calendar_tree(&calendar_infos);

    // Build locale tree
    let locale_tree = build_locale_tree(&locales);

    // Generate the constants file
    let calendar_constants = calendar_ids
        .iter()
        .map(|id| format!("    \"{}\",", id))
        .collect::<Vec<_>>()
        .join("\n");

    let locale_constants = locales
        .iter()
        .map(|locale| format!("    \"{}\",", locale))
        .collect::<Vec<_>>()
        .join("\n");

    // Generate calendar tree constant
    let calendar_tree_constant = generate_calendar_tree_constant(&calendar_tree);

    // Generate locale tree constant
    let locale_tree_constant = generate_locale_tree_constant(&locale_tree);

    // Generate calendar_ids.rs
    let mut calendar_content = String::new();
    calendar_content.push_str("//! Auto-generated calendar constants - Do not modify manually\n");
    calendar_content.push_str("//! This file is generated by build.rs\n\n");

    calendar_content.push_str("/// List of all calendar IDs\n");
    calendar_content.push_str("///\n");
    calendar_content.push_str("/// This constant contains all calendar IDs\n");
    calendar_content.push_str("/// in the romcal project, sorted alphabetically.\n");
    calendar_content.push_str("pub const CALENDAR_IDS: &[&str] = &[\n");
    calendar_content.push_str(&calendar_constants);
    calendar_content.push_str("\n\n];\n\n");

    calendar_content.push_str("/// Calendar tree structure as JSON\n");
    calendar_content.push_str("///\n");
    calendar_content.push_str("/// This constant contains the hierarchical structure\n");
    calendar_content.push_str("/// of all calendars in the romcal project as a JSON string.\n");
    calendar_content.push_str("pub const CALENDAR_TREE_JSON: &str = r#\"");
    calendar_content.push_str(&calendar_tree_constant);
    calendar_content.push_str("\"#;\n");

    fs::write(CALENDAR_IDS_FILE, calendar_content).expect("Failed to write calendar_ids.rs");
    format_generated_file(CALENDAR_IDS_FILE);

    // Generate locale_ids.rs
    let mut locale_content = String::new();
    locale_content.push_str("//! Auto-generated locale constants - Do not modify manually\n");
    locale_content.push_str("//! This file is generated by build.rs\n\n");

    locale_content.push_str("/// List of all locale codes\n");
    locale_content.push_str("///\n");
    locale_content.push_str("/// This constant contains all locale codes\n");
    locale_content.push_str("/// in the romcal project, sorted alphabetically.\n");
    locale_content.push_str("pub const LOCALE_CODES: &[&str] = &[\n");
    locale_content.push_str(&locale_constants);
    locale_content.push_str("\n\n];\n\n");

    locale_content.push_str("/// Locale tree structure as JSON\n");
    locale_content.push_str("///\n");
    locale_content.push_str("/// This constant contains the hierarchical structure\n");
    locale_content.push_str("/// of all locales in the romcal project as a JSON string.\n");
    locale_content
        .push_str("/// The tree follows BCP 47 structure with base languages as parents\n");
    locale_content.push_str("/// and specific locales (e.g., en-gb) as children.\n");
    locale_content.push_str("pub const LOCALE_TREE_JSON: &str = r#\"");
    locale_content.push_str(&locale_tree_constant);
    locale_content.push_str("\"#;\n");

    fs::write(LOCALE_IDS_FILE, locale_content).expect("Failed to write locale_ids.rs");
    format_generated_file(LOCALE_IDS_FILE);

    println!(
        "cargo:info=Constants generated successfully: {} calendars, {} locales",
        calendar_ids.len(),
        locales.len()
    );
}

fn collect_calendar_infos_recursively(dir: &str, infos: &mut Vec<CalendarInfo>) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                // Recursively search subdirectories
                if let Some(dir_str) = path.to_str() {
                    collect_calendar_infos_recursively(dir_str, infos);
                }
            } else if let Some(name) = path.file_name().and_then(|n| n.to_str())
                && name.ends_with(".json")
                && let Ok(content) = fs::read_to_string(&path)
                && let Ok(json) = serde_json::from_str::<serde_json::Value>(&content)
                && let Some(id) = json.get("id").and_then(|v| v.as_str())
            {
                let mut parent_calendar_ids = json
                    .get("parent_calendar_ids")
                    .and_then(|v| v.as_array())
                    .map(|arr| {
                        arr.iter()
                            .filter_map(|v| v.as_str())
                            .map(|s| s.to_string())
                            .collect::<Vec<String>>()
                    })
                    .unwrap_or_default();

                // Add "general_roman" as first parent systematically, except if the current calendar is already general_roman
                if id != "general_roman" {
                    parent_calendar_ids.insert(0, "general_roman".to_string());
                }

                infos.push(CalendarInfo {
                    id: id.to_string(),
                    parent_calendar_ids,
                });
            }
        }
    }
}

fn collect_locale_codes_recursively(dir: &str, locales: &mut HashSet<String>) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                // Recursively search subdirectories
                if let Some(dir_str) = path.to_str() {
                    collect_locale_codes_recursively(dir_str, locales);
                }
            } else if let Some(name) = path.file_name().and_then(|n| n.to_str())
                && name.ends_with(".json")
                && let Ok(content) = fs::read_to_string(&path)
                && let Ok(json) = serde_json::from_str::<serde_json::Value>(&content)
                && let Some(locale) = json.get("locale").and_then(|v| v.as_str())
            {
                locales.insert(locale.to_string());
            }
        }
    }
}

fn format_generated_file(file_path: &str) {
    println!("cargo:info=Formatting generated file with rustfmt...");

    let output = Command::new("rustfmt").arg(file_path).output();

    match output {
        Ok(output) => {
            if output.status.success() {
                println!("cargo:info=File formatted with rustfmt");
            } else {
                println!(
                    "cargo:warning=Warning: rustfmt failed: {}",
                    String::from_utf8_lossy(&output.stderr)
                );
            }
        }
        Err(_) => {
            println!("cargo:warning=Warning: rustfmt not found, skipping formatting");
        }
    }
}

fn main() {
    // Only regenerate constants if data directories exist (development environment).
    // When building from crates.io, these directories won't exist, but the generated
    // files are already included in the package.
    if std::path::Path::new(CALENDARS_DIR).exists() && std::path::Path::new(RESOURCES_DIR).exists()
    {
        generate_constants();
        println!("cargo:rerun-if-changed={}", CALENDARS_DIR);
        println!("cargo:rerun-if-changed={}", RESOURCES_DIR);
    }
}
