//! Resource discovery and registry generation

#[allow(unused_imports)] // Used in generated code via string literals
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};

/// Embed routes.toml content for both SSR and client builds
/// Discovers all route files in config/routes/ directory and combines them
pub fn embed_routes_config(content_root: &str, out_dir: &str) -> Result<(), String> {
    // Get project root more reliably using CARGO_MANIFEST_DIR
    let project_root = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
        // Go up two levels from crates/shared to get project root
        Path::new(&manifest_dir)
            .parent()
            .and_then(|p| p.parent())
            .unwrap_or_else(|| Path::new("."))
            .to_path_buf()
    } else {
        // Fallback: try to detect from content_root
        if content_root.ends_with("/site/content") {
            PathBuf::from(content_root.trim_end_matches("/site/content"))
        } else if content_root.ends_with("site/content") {
            PathBuf::from(content_root.trim_end_matches("site/content"))
        } else {
            PathBuf::from(".")
        }
    };

    // Use SITE_CONFIG_PATH environment variable or default
    let site_config_path =
        std::env::var("SITE_CONFIG_PATH").unwrap_or_else(|_| "site/config".to_string());

    let config_dir = if Path::new(&site_config_path).is_absolute() {
        PathBuf::from(&site_config_path)
    } else {
        project_root.join(&site_config_path)
    };

    let routes_dir = config_dir.join("routes");
    let fallback_routes_file = config_dir.join("routes.toml");

    // Set up rerun triggers for the routes directory
    if routes_dir.exists() {
        println!("cargo:rerun-if-changed={}", routes_dir.to_string_lossy());

        // Also watch individual route files
        if let Ok(entries) = fs::read_dir(&routes_dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.is_file() && path.extension().map_or(false, |ext| ext == "toml") {
                    println!("cargo:rerun-if-changed={}", path.to_string_lossy());
                }
            }
        }
    }

    // Also watch for fallback routes.toml
    println!(
        "cargo:rerun-if-changed={}",
        fallback_routes_file.to_string_lossy()
    );

    let mut combined_routes_content = String::new();
    let mut found_route_files = false;

    // Try to discover and combine route files from content_root/routes/ directory
    if routes_dir.exists() {
        if let Ok(entries) = fs::read_dir(&routes_dir) {
            let mut route_files: Vec<_> = entries
                .flatten()
                .filter(|entry| {
                    let path = entry.path();
                    path.is_file() && path.extension().map_or(false, |ext| ext == "toml")
                })
                .collect();

            // Sort by filename for consistent order
            route_files.sort_by(|a, b| a.file_name().cmp(&b.file_name()));

            for entry in route_files {
                let path = entry.path();
                if let Ok(content) = fs::read_to_string(&path) {
                    // Add a comment header for each file
                    if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
                        combined_routes_content.push_str(&format!(
                            "# {} Configuration\n",
                            filename.trim_end_matches(".toml").to_uppercase()
                        ));
                    }
                    combined_routes_content.push_str(&content);
                    combined_routes_content.push('\n');
                    found_route_files = true;
                }
            }
        }
    }

    // Fallback to single routes.toml file if no individual route files found
    if !found_route_files {
        if let Ok(content) = fs::read_to_string(&fallback_routes_file) {
            combined_routes_content = content;
            found_route_files = true;
        }
    }

    // Final fallback to minimal default config
    if !found_route_files {
        combined_routes_content = r#"
# Default Fallback Routes Configuration
[[routes]]
path = "/"
component = "Home"
page_component = "HomePage"
title_key = "home-page-title"
description_key = "home-page-description"
language = "en"
enabled = true
priority = 1.0
menu_group = "main"
menu_order = 1
menu_icon = "home"
show_in_sitemap = true
keywords = ["home", "rust", "developer"]
canonical_path = "/"
"#
        .to_string();
        eprintln!("Warning: No route files found in {}/routes/ or {}, using minimal fallback configuration", config_dir.display(), fallback_routes_file.display());
    }

    // Write the combined routes content to a file that can be included with include_str!
    let dest_path = Path::new(out_dir).join("embedded_routes.toml");
    fs::write(&dest_path, &combined_routes_content)
        .map_err(|e| format!("Failed to write embedded routes: {e}"))?;

    if found_route_files {
        println!(
            "cargo:warning=Successfully embedded route configurations from {}/routes/",
            config_dir.display()
        );
    }

    Ok(())
}

/// Generate resource registry with automatic discovery
pub fn generate_resource_registry(content_root: &str, out_dir: &str) -> Result<(), String> {
    let content_dir = Path::new(content_root);
    let output_path = Path::new(out_dir).join("resource_registry.rs");

    let mut file =
        File::create(&output_path).map_err(|e| format!("Failed to create output file: {e}"))?;

    writeln!(file, "// Auto-generated resource registry")
        .map_err(|e| format!("Failed to write to file: {e}"))?;
    writeln!(file, "// Contains ALL resources discovered at build time")
        .map_err(|e| format!("Failed to write to file: {e}"))?;
    writeln!(file, "// No hardcoded paths needed!")
        .map_err(|e| format!("Failed to write to file: {e}"))?;
    writeln!(file).map_err(|e| format!("Failed to write to file: {e}"))?;

    writeln!(file, "use std::collections::HashMap;")
        .map_err(|e| format!("Failed to write to file: {e}"))?;
    writeln!(file, "use once_cell::sync::Lazy;")
        .map_err(|e| format!("Failed to write to file: {e}"))?;
    writeln!(file).map_err(|e| format!("Failed to write to file: {e}"))?;

    if content_dir.exists() {
        generate_resource_maps(&content_dir, &mut file)
            .map_err(|e| format!("Failed to generate resource maps: {e}"))?;
    } else {
        // Generate empty maps if content directory doesn't exist
        generate_empty_maps(&mut file)
            .map_err(|e| format!("Failed to generate empty maps: {e}"))?;
    }

    Ok(())
}

fn generate_resource_maps(content_dir: &Path, file: &mut File) -> Result<(), String> {
    // Get project root for UI resources
    let project_root = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
        Path::new(&manifest_dir)
            .parent()
            .and_then(|p| p.parent())
            .unwrap_or_else(|| Path::new("."))
            .to_path_buf()
    } else {
        Path::new(".").to_path_buf()
    };

    // Get UI path from environment variables
    let ui_path = std::env::var("SITE_UI_PATH").unwrap_or_else(|_| {
        let site_root = std::env::var("SITE_ROOT_PATH").unwrap_or_else(|_| "site".to_string());
        format!("{}/ui", site_root)
    });
    let ui_dir = project_root.join(&ui_path);

    // Get themes path (in config/themes, not content)
    let config_path = std::env::var("SITE_CONFIG_PATH").unwrap_or_else(|_| {
        let site_root = std::env::var("SITE_ROOT_PATH").unwrap_or_else(|_| "site".to_string());
        format!("{}/config", site_root)
    });
    let config_dir = project_root.join(&config_path);

    // Discover all resource types from correct locations
    let menus = discover_resources(&ui_dir, "menus");
    let footers = discover_resources(&ui_dir, "footer");
    let themes = discover_resources(&config_dir, "themes");
    let ftl_files = discover_ftl_resources(content_dir);
    let content_json = discover_content_json_resources(content_dir);

    // Generate menu resources
    writeln!(
        file,
        "pub static MENU_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {{"
    )
    .map_err(|e| format!("Failed to write MENU_RESOURCES header: {e}"))?;
    // Only make the map mutable if we have entries to insert
    if menus.is_empty() {
        writeln!(file, "    let map = HashMap::new();")
            .map_err(|e| format!("Failed to write MENU_RESOURCES map init: {e}"))?;
    } else {
        writeln!(file, "    let mut map = HashMap::new();")
            .map_err(|e| format!("Failed to write MENU_RESOURCES map init: {e}"))?;
    }

    for (name, path) in &menus {
        // Use absolute path from root_path instead of fragile relative paths
        let absolute_path = path.to_string_lossy().replace('\\', "/");
        writeln!(
            file,
            "    map.insert(\"{name}\", include_str!(\"{absolute_path}\"));"
        )
        .map_err(|e| format!("Failed to write menu resource {name}: {e}"))?;
    }

    writeln!(file, "    map").map_err(|e| format!("Failed to write MENU_RESOURCES return: {e}"))?;
    writeln!(file, "}});").map_err(|e| format!("Failed to write MENU_RESOURCES close: {e}"))?;
    writeln!(file).map_err(|e| format!("Failed to write newline after MENU_RESOURCES: {e}"))?;

    // Generate footer resources
    writeln!(
        file,
        "pub static FOOTER_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {{"
    )
    .unwrap();
    // Only make the map mutable if we have entries to insert
    if footers.is_empty() {
        writeln!(file, "    let map = HashMap::new();").unwrap();
    } else {
        writeln!(file, "    let mut map = HashMap::new();").unwrap();
    }

    for (name, path) in &footers {
        let absolute_path = path.to_string_lossy().replace('\\', "/");
        writeln!(
            file,
            "    map.insert(\"{name}\", include_str!(\"{absolute_path}\"));"
        )
        .unwrap();
    }

    writeln!(file, "    map").unwrap();
    writeln!(file, "}});").unwrap();
    writeln!(file).unwrap();

    // Generate theme resources
    writeln!(
        file,
        "pub static THEME_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {{"
    )
    .unwrap();
    // Only make the map mutable if we have entries to insert
    if themes.is_empty() {
        writeln!(file, "    let map = HashMap::new();").unwrap();
    } else {
        writeln!(file, "    let mut map = HashMap::new();").unwrap();
    }

    for (name, path) in &themes {
        let absolute_path = path.to_string_lossy().replace('\\', "/");
        writeln!(
            file,
            "    map.insert(\"{name}\", include_str!(\"{absolute_path}\"));"
        )
        .unwrap();
    }

    writeln!(file, "    map").unwrap();
    writeln!(file, "}});").unwrap();
    writeln!(file).unwrap();

    // Generate FTL resources
    writeln!(
        file,
        "pub static FTL_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {{"
    )
    .unwrap();
    // Only make the map mutable if we have entries to insert
    if ftl_files.is_empty() {
        writeln!(file, "    let map = HashMap::new();").unwrap();
    } else {
        writeln!(file, "    let mut map = HashMap::new();").unwrap();
    }

    for (name, path) in &ftl_files {
        let absolute_path = path.to_string_lossy().replace('\\', "/");
        writeln!(
            file,
            "    map.insert(\"{name}\", include_str!(\"{absolute_path}\"));"
        )
        .unwrap();
    }

    writeln!(file, "    map").unwrap();
    writeln!(file, "}});").unwrap();
    writeln!(file).unwrap();

    // Generate content JSON resources
    writeln!(
        file,
        "pub static CONTENT_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {{"
    )
    .unwrap();
    // Only make the map mutable if we have entries to insert
    if content_json.is_empty() {
        writeln!(file, "    let map = HashMap::new();").unwrap();
    } else {
        writeln!(file, "    let mut map = HashMap::new();").unwrap();
    }

    for (name, path) in &content_json {
        let absolute_path = path.to_string_lossy().replace('\\', "/");
        writeln!(
            file,
            "    map.insert(\"{name}\", include_str!(\"{absolute_path}\"));"
        )
        .unwrap();
    }

    writeln!(file, "    map").unwrap();
    writeln!(file, "}});").unwrap();
    writeln!(file).unwrap();

    // Generate discovery functions
    generate_discovery_functions(file, &menus, &footers, &themes, &ftl_files, &content_json)
        .map_err(|e| format!("Failed to generate discovery functions: {e}"))?;

    Ok(())
}

fn discover_resources(content_dir: &Path, subdir: &str) -> Vec<(String, PathBuf)> {
    let mut resources = Vec::new();
    let resource_dir = content_dir.join(subdir);

    if resource_dir.exists() {
        if let Ok(entries) = fs::read_dir(&resource_dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.is_file() {
                    if let Some(extension) = path.extension() {
                        if extension == "toml" {
                            if let Some(stem) = path.file_stem() {
                                if let Some(name) = stem.to_str() {
                                    resources.push((name.to_string(), path));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    resources
}

fn discover_ftl_resources(_content_dir: &Path) -> Vec<(String, PathBuf)> {
    let mut resources = Vec::new();

    // Get project root for i18n resources
    let project_root = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
        Path::new(&manifest_dir)
            .parent()
            .and_then(|p| p.parent())
            .unwrap_or_else(|| Path::new("."))
            .to_path_buf()
    } else {
        Path::new(".").to_path_buf()
    };

    // Get i18n/locales path from environment variables
    let i18n_path = std::env::var("SITE_I18N_PATH").unwrap_or_else(|_| {
        let site_root = std::env::var("SITE_ROOT_PATH").unwrap_or_else(|_| "site".to_string());
        format!("{}/i18n", site_root)
    });
    let locales_dir = project_root.join(&i18n_path).join("locales");

    if locales_dir.exists() {
        // Walk through language directories (en, es, etc.)
        if let Ok(lang_entries) = fs::read_dir(&locales_dir) {
            for lang_entry in lang_entries.flatten() {
                let lang_path = lang_entry.path();
                if lang_path.is_dir() {
                    if let Some(lang_name) = lang_path.file_name().and_then(|n| n.to_str()) {
                        collect_ftl_files(&lang_path, lang_name, &mut resources);
                    }
                }
            }
        }
    }

    resources
}

fn collect_ftl_files(dir: &Path, lang: &str, resources: &mut Vec<(String, PathBuf)>) {
    collect_ftl_files_with_prefix(dir, lang, "", resources);
}

fn collect_ftl_files_with_prefix(
    dir: &Path,
    lang: &str,
    prefix: &str,
    resources: &mut Vec<(String, PathBuf)>,
) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_file() {
                if let Some(extension) = path.extension() {
                    if extension == "ftl" {
                        if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                            let key = if prefix.is_empty() {
                                format!("{lang}_{stem}")
                            } else {
                                format!("{lang}_{prefix}_{stem}")
                            };
                            resources.push((key, path));
                        }
                    }
                }
            } else if path.is_dir() {
                // Recursively collect FTL files from subdirectories
                if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
                    let new_prefix = if prefix.is_empty() {
                        dir_name.to_string()
                    } else {
                        format!("{prefix}_{dir_name}")
                    };
                    collect_ftl_files_with_prefix(&path, lang, &new_prefix, resources);
                }
            }
        }
    }
}

fn discover_content_json_resources(content_dir: &Path) -> Vec<(String, PathBuf)> {
    let mut resources = Vec::new();

    // Build script runs from crate directory, need to find workspace root
    let current_dir = std::env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());

    // Find workspace root by looking for Cargo.toml with [workspace]
    let workspace_root = find_workspace_root(&current_dir).unwrap_or(current_dir);

    let processed_content_root = {
        let public_path = std::env::var("SITE_PUBLIC_PATH").unwrap_or_else(|_| "public".to_string());
        let server_root_content = std::env::var("SITE_SERVER_ROOT_CONTENT").unwrap_or_else(|_| "r".to_string());
        format!("{}/{}", public_path, server_root_content)
    };

    // Make path relative to workspace root
    let processed_content_dir = workspace_root.join(&processed_content_root);


    if !processed_content_dir.exists() {
        println!("cargo:warning=DEBUG: Processed content directory {} does not exist - content resources will be empty", processed_content_dir.display());
        return resources;
    }

    // Dynamically discover content types from content-kinds.toml (use original content_dir for config)
    let content_types = discover_available_content_types(content_dir);

    for content_type in content_types {
        let content_type_dir = processed_content_dir.join(&content_type);
        if content_type_dir.exists() {
            collect_json_files(&content_type_dir, &content_type, &mut resources);
            collect_markdown_files(&content_type_dir, &content_type, &mut resources);
        }
    }

    resources
}

/// Discover available content types from content-kinds.toml
fn discover_available_content_types(content_dir: &Path) -> Vec<String> {
    let config_path = content_dir.join("content-kinds.toml");

    if !config_path.exists() {
        // Fallback to directory-based discovery if config is missing
        return discover_content_types_from_directories(content_dir);
    }

    match std::fs::read_to_string(&config_path) {
        Ok(config_content) => {
            match toml::from_str::<crate::build::build_tasks::content_types::ContentKindsConfig>(
                &config_content,
            ) {
                Ok(config) => config
                    .content_kinds
                    .into_iter()
                    .filter(|kind| kind.enabled)
                    .map(|kind| kind.directory)
                    .collect(),
                Err(e) => {
                    eprintln!("Warning: Failed to parse content-kinds.toml: {}", e);
                    discover_content_types_from_directories(content_dir)
                }
            }
        }
        Err(e) => {
            eprintln!("Warning: Could not read content-kinds.toml: {}", e);
            discover_content_types_from_directories(content_dir)
        }
    }
}

/// Fallback: discover content types by scanning directories
fn discover_content_types_from_directories(content_dir: &Path) -> Vec<String> {
    let mut content_types = Vec::new();

    if let Ok(entries) = std::fs::read_dir(content_dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
                    // Skip known non-content directories
                    if !["locales", "themes", "menus", "footer", "routes", "tmp"]
                        .contains(&dir_name)
                    {
                        content_types.push(dir_name.to_string());
                    }
                }
            }
        }
    }

    content_types.sort();
    content_types
}

fn collect_json_files(dir: &Path, content_type: &str, resources: &mut Vec<(String, PathBuf)>) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                // Check for language directories (en, es, etc.)
                if let Some(lang_name) = path.file_name().and_then(|n| n.to_str()) {
                    let index_file = path.join("index.json");
                    if index_file.exists() {
                        let key = format!("{content_type}_{lang_name}_index");
                        resources.push((key, index_file));
                    }
                }
            }
        }
    }
}

fn collect_markdown_files(dir: &Path, content_type: &str, resources: &mut Vec<(String, PathBuf)>) {
    if let Ok(entries) = fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_dir() {
                // Check for language directories (en, es, etc.)
                if let Some(lang_name) = path.file_name().and_then(|n| n.to_str()) {
                    // Collect all markdown files in this language directory
                    if let Ok(md_entries) = fs::read_dir(&path) {
                        for md_entry in md_entries.flatten() {
                            let md_path = md_entry.path();
                            if md_path.is_file() {
                                if let Some(extension) = md_path.extension() {
                                    if extension == "md" {
                                        if let Some(stem) =
                                            md_path.file_stem().and_then(|s| s.to_str())
                                        {
                                            let key = format!("{content_type}_{lang_name}_{stem}");
                                            resources.push((key, md_path));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

fn generate_empty_maps(file: &mut File) -> Result<(), String> {
    writeln!(file, "pub static MENU_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(HashMap::new);")
        .map_err(|e| format!("Failed to write empty MENU_RESOURCES: {e}"))?;
    writeln!(file, "pub static FOOTER_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(HashMap::new);")
        .map_err(|e| format!("Failed to write empty FOOTER_RESOURCES: {e}"))?;
    writeln!(file, "pub static THEME_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(HashMap::new);")
        .map_err(|e| format!("Failed to write empty THEME_RESOURCES: {e}"))?;
    writeln!(file, "pub static FTL_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(HashMap::new);")
        .map_err(|e| format!("Failed to write empty FTL_RESOURCES: {e}"))?;
    writeln!(file, "pub static CONTENT_RESOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(HashMap::new);")
        .map_err(|e| format!("Failed to write empty CONTENT_RESOURCES: {e}"))?;
    writeln!(file).map_err(|e| format!("Failed to write newline: {e}"))?;

    // Generate empty discovery functions
    writeln!(
        file,
        "pub fn available_menus() -> Vec<&'static str> {{ vec![] }}"
    )
    .map_err(|e| format!("Failed to write available_menus: {e}"))?;
    writeln!(
        file,
        "pub fn available_footers() -> Vec<&'static str> {{ vec![] }}"
    )
    .map_err(|e| format!("Failed to write available_footers: {e}"))?;
    writeln!(
        file,
        "pub fn available_themes() -> Vec<&'static str> {{ vec![] }}"
    )
    .map_err(|e| format!("Failed to write available_themes: {e}"))?;
    writeln!(
        file,
        "pub fn available_ftl_resources() -> Vec<&'static str> {{ vec![] }}"
    )
    .map_err(|e| format!("Failed to write available_ftl_resources: {e}"))?;
    writeln!(
        file,
        "pub fn available_content_resources() -> Vec<&'static str> {{ vec![] }}"
    )
    .map_err(|e| format!("Failed to write available_content_resources: {e}"))?;

    Ok(())
}

/// Find workspace root by looking for Cargo.toml with [workspace] section
fn find_workspace_root(start_dir: &Path) -> Option<PathBuf> {
    let mut current_dir = start_dir;

    loop {
        let cargo_toml = current_dir.join("Cargo.toml");

        if cargo_toml.exists() {
            // Check if this Cargo.toml contains [workspace]
            if let Ok(cargo_content) = std::fs::read_to_string(&cargo_toml) {
                if cargo_content.contains("[workspace]") {
                    return Some(current_dir.to_path_buf());
                }
            }
        }

        // Go up one directory
        if let Some(parent) = current_dir.parent() {
            current_dir = parent;
        } else {
            break;
        }
    }

    None
}

fn generate_discovery_functions(
    file: &mut File,
    menus: &[(String, PathBuf)],
    footers: &[(String, PathBuf)],
    themes: &[(String, PathBuf)],
    ftl_files: &[(String, PathBuf)],
    content_json: &[(String, PathBuf)],
) -> Result<(), String> {
    // Generate available_menus function
    writeln!(file, "pub fn available_menus() -> Vec<&'static str> {{").unwrap();
    writeln!(file, "    vec![").unwrap();
    for (name, _) in menus {
        writeln!(file, "        \"{name}\",").unwrap();
    }
    writeln!(file, "    ]").unwrap();
    writeln!(file, "}}").unwrap();
    writeln!(file).unwrap();

    // Generate available_footers function
    writeln!(file, "pub fn available_footers() -> Vec<&'static str> {{").unwrap();
    writeln!(file, "    vec![").unwrap();
    for (name, _) in footers {
        writeln!(file, "        \"{name}\",").unwrap();
    }
    writeln!(file, "    ]").unwrap();
    writeln!(file, "}}").unwrap();
    writeln!(file).unwrap();

    // Generate available_themes function
    writeln!(file, "pub fn available_themes() -> Vec<&'static str> {{").unwrap();
    writeln!(file, "    vec![").unwrap();
    for (name, _) in themes {
        writeln!(file, "        \"{name}\",").unwrap();
    }
    writeln!(file, "    ]").unwrap();
    writeln!(file, "}}").unwrap();
    writeln!(file).unwrap();

    // Generate available_ftl_resources function
    writeln!(
        file,
        "pub fn available_ftl_resources() -> Vec<&'static str> {{"
    )
    .unwrap();
    writeln!(file, "    vec![").unwrap();
    for (name, _) in ftl_files {
        writeln!(file, "        \"{name}\",").unwrap();
    }
    writeln!(file, "    ]").unwrap();
    writeln!(file, "}}").unwrap();
    writeln!(file).unwrap();

    // Generate available_content_resources function
    writeln!(
        file,
        "pub fn available_content_resources() -> Vec<&'static str> {{"
    )
    .unwrap();
    writeln!(file, "    vec![").unwrap();
    for (name, _) in content_json {
        writeln!(file, "        \"{name}\",").unwrap();
    }
    writeln!(file, "    ]").map_err(|e| format!("Failed to write content_resources close: {e}"))?;
    writeln!(file, "}}").map_err(|e| format!("Failed to write content_resources end: {e}"))?;

    Ok(())
}
