// Page generation system for pages crate using rustelo_tools SmartCache
// Uses route configuration to generate proper Client/SSR components

use serde::{Deserialize, Serialize};
use std::env;
use std::fs;
use std::path::Path;

/// Determine if a component needs ::unified in import path
/// This is PAP-compliant as it uses the pattern from rustelo_pages lib.rs
/// Components that are direct files (like external_link) don't need ::unified
/// Components that are directories with unified.rs files need ::unified
fn needs_unified_import(component_name: &str, import_package: &str) -> bool {
    // Only foundation components (rustelo_pages) use ::unified structure
    // Local website components (website_pages) never use ::unified
    if import_package == "website_pages" {
        return false;
    }

    // Based on rustelo_pages/src/lib.rs, external_link is the only direct file
    // All other foundation components are directories with unified.rs submodules
    component_name != "external_link"
}

/// Route configuration structure
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct RouteConfig {
    pub path: String,
    pub page_component: String,
    pub language: String,
    pub enabled: bool,
    #[serde(default)]
    pub unified_component: Option<String>,
    #[serde(default)]
    pub lang_prefixes: Option<Vec<String>>,
    #[serde(default)]
    pub props: Option<toml::Value>,
    #[serde(default)]
    pub content_type: Option<String>,
    #[serde(default)]
    pub module_path: Option<String>,
    #[serde(default)]
    pub parameter_extraction: Option<toml::Value>,
    #[serde(default)]
    pub content_type_param: Option<toml::Value>,
}

/// Component generation information
#[derive(Debug, Clone, Serialize)]
pub struct ComponentInfo {
    pub component_name: String,
    pub unified_component: String,
    pub lang_prefixes: Vec<String>,
    pub props: Option<toml::Value>,
    pub has_existing_unified: bool,
    pub has_local_unified: bool,
    pub is_content_page: bool,
    pub _module_path: String,
    pub route_config: RouteConfig,
}

/// Extract the actual component name from the import path, handling aliases
fn get_actual_component_name(import_path: &str) -> String {
    if import_path.contains(" as ") {
        // Handle "Component as Alias" format
        import_path
            .split(" as ")
            .last()
            .unwrap_or(import_path)
            .to_string()
    } else {
        // Handle "path::Component" format
        import_path
            .split("::")
            .last()
            .unwrap_or(import_path)
            .to_string()
    }
}

/// Get required props for component from route configuration
fn get_component_required_props_from_config(
    route_config: &RouteConfig,
) -> (String, String, String) {
    let mut prop_defs = Vec::new();
    let mut prop_args = Vec::new();
    let mut prop_forwards = Vec::new();
    let mut clones = Vec::new();

    // Check if this is a category page (has category parameter)
    let path = &route_config.path;
    let is_content_index = route_config.page_component.contains("ContentIndex");
    if path.contains("{category}") {
        if is_content_index {
            // ContentIndex maps {category} to filter: String (empty = no filter).
            // Leptos #[prop(optional)] on Option<String> takes String not Option<String>,
            // so we keep filter as String across all delegation layers.
            prop_defs.push("#[prop(default = String::new())] filter: String");
            prop_args.push("filter=filter.clone()");
            prop_forwards.push("filter=filter.clone()");
            clones.push("filter");
        } else {
            prop_defs.push("category: String");
            prop_args.push("category=category");
            prop_forwards.push("category=category.clone()");
            clones.push("category");
        }
    }
    if path.contains("{slug}") {
        prop_defs.push("slug: String");
        prop_args.push("slug=slug");
        prop_forwards.push("slug=slug.clone()");
        clones.push("slug");
    }

    // Check parameter_extraction section for required params
    if let Some(toml::Value::Table(ref param_extraction)) = route_config.parameter_extraction {
        for (param_name, _extraction_method) in param_extraction {
            match param_name.as_str() {
                "category" if !clones.contains(&"category") && !clones.contains(&"filter") => {
                    if is_content_index {
                        prop_defs.push("#[prop(default = String::new())] filter: String");
                        prop_args.push("filter=filter.clone()");
                        prop_forwards.push("filter=filter.clone()");
                        clones.push("filter");
                    } else {
                        prop_defs.push("category: String");
                        prop_args.push("category=category");
                        prop_forwards.push("category=category.clone()");
                        clones.push("category");
                    }
                }
                "slug" if !clones.contains(&"slug") => {
                    prop_defs.push("slug: String");
                    prop_args.push("slug=slug");
                    prop_forwards.push("slug=slug.clone()");
                    clones.push("slug");
                }
                _ => {} // Ignore other parameters
            }
        }
    }

    // Add content_type as parameter if content_type_param is true OR if content_type is in props
    let needs_content_type = match route_config.content_type_param {
        Some(toml::Value::Boolean(true)) => true,
        _ => {
            // Also check if content_type is defined in props section
            route_config
                .props
                .as_ref()
                .and_then(|props| props.as_table())
                .map(|table| table.contains_key("content_type"))
                .unwrap_or(false)
        }
    };

    if needs_content_type && !clones.contains(&"content_type") {
        prop_defs.push("content_type: String");
        prop_args.push("content_type=content_type");
        prop_forwards.push("content_type=content_type.clone()");
        clones.push("content_type");
    }

    let props_str = if !prop_defs.is_empty() {
        format!("\n    {},", prop_defs.join(",\n    "))
    } else {
        "".to_string()
    };

    let args_str = if !prop_args.is_empty() {
        format!(" {}", prop_args.join(" "))
    } else {
        "".to_string()
    };

    let forwards_str = if !prop_forwards.is_empty() {
        format!("\n            {}", prop_forwards.join("\n            "))
    } else {
        "".to_string()
    };

    (props_str, args_str, forwards_str)
}

/// Check if component accepts lang_content prop from route configuration
fn component_accepts_lang_content_from_config(route_config: &RouteConfig) -> bool {
    // Check if route explicitly defines lang_content in props
    if let Some(toml::Value::Table(props_table)) = &route_config.props {
        if props_table.contains_key("lang_content") {
            return true;
        }
    }

    // Check if route has specific properties that indicate it needs lang_content
    if let Some(component) = &route_config.unified_component {
        // External links typically don't need lang_content
        if component.contains("External") {
            return false;
        }
        // Content pages (blog, recipes) need lang_content for i18n
        if component.contains("ContentIndex") || component.contains("ContentCategory") {
            return true;
        }
    }

    // Default: all page components accept lang_content (it's #[prop(optional)] on all Unified*Page)
    // External links are the only exception (already handled above)
    true
}

/// Derive the rustelo_pages `src/` directory from the workspace root.
///
/// Reads `workspace_root/Cargo.toml`, extracts the `rustelo_pages` path value,
/// and resolves it to an absolute `src/` path.
fn rustelo_pages_src(workspace_root: &Path) -> Option<std::path::PathBuf> {
    let cargo_toml = workspace_root.join("Cargo.toml");
    let content = std::fs::read_to_string(&cargo_toml).ok()?;

    for line in content.lines() {
        let trimmed = line.trim();
        if trimmed.starts_with("rustelo_pages_leptos") && trimmed.contains("path") {
            // Extract the value inside the first pair of double-quotes
            let mut chars = trimmed.chars();
            if let Some(start) = chars.by_ref().position(|c| c == '"') {
                let after_open: String = trimmed[start + 1..].to_string();
                if let Some(end) = after_open.find('"') {
                    let rel = &after_open[..end];
                    let src = workspace_root.join(rel).join("src");
                    return src.canonicalize().ok();
                }
            }
        }
    }
    None
}

/// Resolve the Rust module path for a snake_case component name within a `src/` tree.
///
/// Tries all possible underscore-split points until it finds a directory containing
/// `unified.rs`. Returns e.g. `"content::index"` for `"content_index"`.
fn resolve_module_path_in_src(src: &Path, snake: &str) -> Option<String> {
    // Single-segment check first
    if src.join(snake).join("unified.rs").exists() {
        return Some(snake.to_string());
    }
    if src.join(format!("{snake}.rs")).exists() {
        return Some(snake.to_string());
    }

    // Try all underscore split points for multi-segment paths
    let parts: Vec<&str> = snake.split('_').collect();
    for split_at in 1..parts.len() {
        let prefix = parts[..split_at].join("_");
        let suffix = parts[split_at..].join("_");
        let candidate = src.join(&prefix).join(&suffix);
        if candidate.join("unified.rs").exists() {
            return Some(format!("{prefix}::{suffix}"));
        }
    }
    None
}

/// Generate unified component import path from route configuration.
///
/// `workspace_root` is used to locate the `rustelo_pages` source tree when the
/// component lives in the foundation crate (so we can resolve multi-level module
/// paths like `content::index` instead of the flattened `content_index`).
/// `pages_src` is the `src/` directory of the website pages crate, used to check
/// whether a local `unified.rs` submodule exists for website_pages components.
fn get_unified_component_path_from_config(
    route_config: &RouteConfig,
    component_info: &ComponentInfo,
    import_package: &str,
    workspace_root: &Path,
    pages_src: Option<&Path>,
) -> String {
    let unified_component = &component_info.unified_component;

    // Explicit module_path in route config takes top priority
    if let Some(module_path) = &route_config.module_path {
        let rust_path = module_path.replace("/", "::");
        if needs_unified_import(&rust_path.replace("::", "_"), import_package) {
            return format!("{}::unified::{}", rust_path, unified_component);
        } else {
            return format!("{}::{}", rust_path, unified_component);
        }
    }

    // Derive snake_case component base name (strip Unified…Page wrapper)
    let bare = unified_component
        .strip_prefix("Unified")
        .unwrap_or(unified_component)
        .strip_suffix("Page")
        .unwrap_or(unified_component);

    let snake: String = bare
        .chars()
        .enumerate()
        .map(|(i, c)| {
            if c.is_uppercase() && i > 0 {
                format!("_{}", c.to_lowercase())
            } else {
                c.to_lowercase().to_string()
            }
        })
        .collect();

    // For foundation components, search the actual rustelo_pages src tree to
    // find the real multi-level module path (e.g. content::index, not content_index).
    if import_package == "rustelo_pages_leptos" {
        if let Some(src) = rustelo_pages_src(workspace_root) {
            if let Some(found) = resolve_module_path_in_src(&src, &snake) {
                if found.contains("::") {
                    // Multi-level path (e.g. content::index) always has unified.rs
                    return format!("{}::unified::{}", found, unified_component);
                }
                // Single segment: dir with unified.rs or direct file
                return if src.join(&snake).join("unified.rs").exists() {
                    format!("{}::unified::{}", found, unified_component)
                } else {
                    // Direct file (e.g. external_link.rs) — no ::unified:: in path
                    format!("{}::{}", found, unified_component)
                };
            }
        }
    }

    // Fallback: use snake_case path as-is.
    // For website_pages, check the actual src tree for a unified.rs submodule
    // (website pages follow the same dir/unified.rs convention as rustelo_pages).
    if import_package == "website_pages" {
        let has_unified = pages_src
            .map(|src| src.join(&snake).join("unified.rs").exists())
            .unwrap_or(false);
        return if has_unified {
            format!("{}::unified::{}", snake, unified_component)
        } else {
            format!("{}::{}", snake, unified_component)
        };
    }

    if needs_unified_import(&snake, import_package) {
        format!("{}::unified::{}", snake, unified_component)
    } else {
        format!("{}::{}", snake, unified_component)
    }
}

/// Generate page components using rustelo_tools SmartCache.
///
/// `workspace_root` must be the absolute path to the project workspace root
/// (the directory containing `rustelo.manifest.toml`). It is provided by
/// `build.rs` via `ManifestResolver::load()?.root()` to avoid a redundant
/// manifest search with a potentially different CWD.
pub fn generate_page_components(workspace_root: &Path) -> Result<(), Box<dyn std::error::Error>> {
    let out_dir = env::var("OUT_DIR")?;
    let out_path = Path::new(&out_dir);

    // Check if smart cache is available
    if !rustelo_tools::is_cache_available() {
        println!("cargo:warning=Smart cache not available - using direct generation");
        return generate_components_direct(out_path, workspace_root);
    }

    // Initialize smart cache for pages
    let cache = rustelo_tools::SmartCache::new("pages")?;

    // Load route configurations
    let components = load_route_configurations(workspace_root)?;
    let components_count = components.len();

    println!(
        "cargo:warning=Using smart cache for {} page components",
        components_count
    );

    for component_info in components {
        if component_info.has_existing_unified {
            let filename = format!("page_{}.rs", component_info.component_name);

            // Calculate hash for this specific component's config
            let component_route_content = serde_json::to_string(&component_info)?;
            let dependencies = vec![get_route_config_path(workspace_root)];
            let source_hash =
                rustelo_tools::hash_route_config(&component_route_content, &dependencies);

            // Check cache status
            match cache.check_file_cache(&filename, &source_hash)? {
                rustelo_tools::CacheStatus::Fresh(_) => {
                    // Use cached version - fast path!
                    cache.copy_from_cache(&filename, out_path)?;
                    println!("cargo:warning=Cached: {}", filename);
                }
                rustelo_tools::CacheStatus::Stale(_) | rustelo_tools::CacheStatus::Missing(_) => {
                    // Generate new component - slow path
                    let content = generate_single_component_content(
                        &component_info,
                        &component_info.route_config,
                        workspace_root,
                    )?;
                    cache.update_cache(
                        &filename,
                        &content,
                        &source_hash,
                        out_path,
                        dependencies,
                    )?;
                    println!("cargo:warning=Generated: {}", filename);
                }
            }

            // Copy to reviewable location (build-cache style)
            copy_to_reviewable_location(&filename, out_path)?;
        }
    }

    // Always show final summary
    println!(
        "cargo:warning=✅ Generated {} page components with foundation virtual resolution",
        components_count
    );

    Ok(())
}

/// Direct generation without caching (fallback)
fn generate_components_direct(
    out_path: &Path,
    workspace_root: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    // Clean output directory
    clean_generated_output(out_path)?;

    // Load route configurations
    let components = load_route_configurations(workspace_root)?;

    for component_info in components {
        if component_info.has_existing_unified {
            // Generate wrapper components for existing unified pages
            let content = generate_single_component_content(
                &component_info,
                &component_info.route_config,
                workspace_root,
            )?;
            let filename = format!("page_{}.rs", component_info.component_name);
            let file_path = out_path.join(&filename);
            fs::write(&file_path, content)?;
            println!(
                "cargo:warning=Generated page component: {}",
                component_info.component_name
            );

            // Copy to reviewable location
            copy_to_reviewable_location(&filename, out_path)?;
        }
    }

    Ok(())
}

/// Return the config dependency path for cache invalidation.
///
/// # Panics
/// Panics if `SITE_CONFIG_PATH` is not set — callers must configure it before
/// invoking build scripts.
fn get_route_config_path(_workspace_root: &Path) -> String {
    std::env::var("SITE_CONFIG_PATH").unwrap_or_else(|_| {
        panic!(
            "\n\nSITE_CONFIG_PATH is not set.\n\
             Set it to your site config file before building:\n\
             \n  export SITE_CONFIG_PATH=/path/to/site/config/index.ncl\n"
        )
    })
}

/// Generate wrapper component content.
///
/// `workspace_root` is required to resolve multi-level module paths in
/// `rustelo_pages` (e.g. `content::index` vs the flattened `content_index`).
fn generate_single_component_content(
    component_info: &ComponentInfo,
    route_config: &RouteConfig,
    workspace_root: &Path,
) -> Result<String, Box<dyn std::error::Error>> {
    if !component_info.has_existing_unified {
        return Err(format!(
            "Cannot generate wrapper for {}: unified component does not exist",
            component_info.component_name
        )
        .into());
    }

    // Build i18n patterns: for content pages, always include "content-" (shared card keys
    // like content-view-all) plus the content_type-specific prefix.
    let patterns = if component_info.is_content_page {
        if let Some(toml::Value::Table(ref props_table)) = component_info.props {
            if let Some(toml::Value::String(content_type)) = props_table.get("content_type") {
                vec![
                    "content-".to_string(),
                    format!("{}-", content_type.to_lowercase()),
                ]
            } else {
                let mut p = component_info.lang_prefixes.clone();
                if !p.iter().any(|s| s == "content-") {
                    p.insert(0, "content-".to_string());
                }
                p
            }
        } else {
            let mut p = component_info.lang_prefixes.clone();
            if !p.iter().any(|s| s == "content-") {
                p.insert(0, "content-".to_string());
            }
            p
        }
    } else {
        let mut p = component_info.lang_prefixes.clone();
        if !p.iter().any(|s| s == "common-") {
            p.push("common-".to_string());
        }
        p
    };

    let patterns_str = patterns
        .iter()
        .map(|p| format!("\"{}\"", p))
        .collect::<Vec<_>>()
        .join(", ");

    // Get required props from route configuration (PAP-compliant)
    let (all_props, all_args, all_forward) = get_component_required_props_from_config(route_config);

    // Create forward strings for both client and SSR
    let all_forward_client = if !all_forward.is_empty() {
        format!(
            "\n                {}",
            all_forward.replace("\n            ", "\n                ")
        )
    } else {
        "".to_string()
    };

    let all_forward_ssr = if !all_forward.is_empty() {
        format!("\n            {}", all_forward)
    } else {
        "".to_string()
    };

    // Generate clones based on actual props from configuration
    let all_clones = if !all_props.is_empty() {
        let mut clone_statements = Vec::new();
        if all_props.contains("category") {
            clone_statements.push("let category = category.clone();");
        }
        if all_props.contains("content_type") {
            clone_statements.push("let content_type = content_type.clone();");
        }
        if all_props.contains("slug") {
            clone_statements.push("let slug = slug.clone();");
        }
        if !clone_statements.is_empty() {
            format!("\n    {}", clone_statements.join("\n    "))
        } else {
            "".to_string()
        }
    } else {
        "".to_string()
    };

    // Check if component accepts lang_content from configuration
    let accepts_lang_content = component_accepts_lang_content_from_config(route_config);
    let lang_content_prop = if accepts_lang_content {
        "\n            lang_content=lang_content.clone()".to_string()
    } else {
        "".to_string()
    };
    let lang_content_prop_ssr = if accepts_lang_content {
        "\n            lang_content=lang_content.clone()".to_string()
    } else {
        "".to_string()
    };
    let trans_res_setup = "".to_string();
    let lang_content_compute = if accepts_lang_content {
        format!(
            "let patterns: Vec<&str> = vec![{}];\n    let translator = rustelo_core_lib::SsrTranslator::new(_language.clone());\n    let lang_content = rustelo_core_lib::i18n::helpers::build_page_content_patterns(&translator, &patterns);",
            patterns_str
        )
    } else {
        "".to_string()
    };

    // Determine which package to import from.
    // A component is website-specific if its module_path maps to an existing
    // directory inside the pages crate's src/ tree.
    let pages_src = env::var("CARGO_MANIFEST_DIR")
        .ok()
        .map(|d| std::path::Path::new(&d).join("src"))
        .filter(|p| p.exists());

    let import_package = if let Some(module_path) = &route_config.module_path {
        if let Some(ref src) = pages_src {
            let module_dir = src.join(module_path.replace("::", "/"));
            if module_dir.exists() && module_dir.is_dir() {
                "website_pages"
            } else {
                "rustelo_pages_leptos"
            }
        } else {
            "rustelo_pages_leptos"
        }
    } else {
        // Components without an explicit module_path are foundation components.
        // Still check by snake_case component name in pages_src for local overrides.
        let snake: String = {
            let bare = component_info
                .unified_component
                .strip_prefix("Unified")
                .unwrap_or(&component_info.unified_component)
                .strip_suffix("Page")
                .unwrap_or(&component_info.unified_component);
            bare.chars()
                .enumerate()
                .map(|(i, c)| {
                    if c.is_uppercase() && i > 0 {
                        format!("_{}", c.to_lowercase())
                    } else {
                        c.to_lowercase().to_string()
                    }
                })
                .collect()
        };
        if pages_src.as_ref().is_some_and(|s| s.join(&snake).is_dir()) {
            "website_pages"
        } else {
            "rustelo_pages_leptos"
        }
    };

    let content = format!(
        r#"
// Auto-generated wrapper components for {component_name}
// Calls existing {unified_component} from unified module

#[allow(unused_imports)]
use leptos::prelude::*;
use {import_package}::{unified_component_path};

/// Client-side reactive page component (wrapper)
#[component]
pub fn {component_name}Client(
    #[prop(default = rustelo_core_lib::config::get_default_language().to_string())] _language: String,{all_props}
) -> impl IntoView {{
    {trans_res_setup}
    // Clone language for use in closure
    let language = _language.clone();{all_clones}
    {lang_content_compute}

    view! {{
        <{actual_component}
            _language=language.clone(){lang_content_prop}{all_forward_client}
        />
    }}.into_any()
}}

/// SSR static page component (wrapper)
#[component]
pub fn {component_name}SSR(
    #[prop(default = rustelo_core_lib::config::get_default_language().to_string())] _language: String,{all_props}
) -> impl IntoView {{
    // Create static content for SSR using available translator
    let patterns: Vec<&str> = vec![{patterns_str}];
    let translator = rustelo_core_lib::SsrTranslator::new(_language.clone());
    let lang_content = rustelo_core_lib::i18n::helpers::build_page_content_patterns(&translator, &patterns);

    view! {{
        <{actual_component}
            _language=_language.clone(){lang_content_prop_ssr}{all_forward_ssr}
        />
    }}.into_any()
}}

/// Main page component with delegation
#[component]
pub fn {component_name}Page(
    #[prop(default = rustelo_core_lib::config::get_default_language().to_string())] _language: String,{all_props}
) -> impl IntoView {{
    #[cfg(not(target_arch = "wasm32"))]
    {{
        view! {{ <{component_name}SSR _language=_language.clone(){all_forward_as_args} /> }}.into_any()
    }}

    #[cfg(target_arch = "wasm32")]
    {{
        view! {{ <{component_name}Client _language=_language.clone(){all_forward_as_args} /> }}.into_any()
    }}
}}
"#,
        component_name = component_info.component_name,
        unified_component = component_info.unified_component,
        unified_component_path = get_unified_component_path_from_config(
            route_config,
            component_info,
            import_package,
            workspace_root,
            pages_src.as_deref()
        ),
        actual_component = get_actual_component_name(&get_unified_component_path_from_config(
            route_config,
            component_info,
            import_package,
            workspace_root,
            pages_src.as_deref()
        )),
        patterns_str = patterns_str,
        all_props = all_props,
        all_clones = all_clones,
        all_forward_as_args = all_args,
        import_package = import_package
    );

    Ok(content)
}

/// Load route configurations from site/config.ncl (single source of truth).
///
/// `workspace_root` is the project workspace root containing `site/config.ncl`.
/// It is threaded down from `build.rs` via `ManifestResolver::load()?.root()`.
fn load_route_configurations(
    workspace_root: &Path,
) -> Result<Vec<ComponentInfo>, Box<dyn std::error::Error>> {
    use std::collections::HashMap;

    let site_cfg = build_config::site_config::load_unified_site_config(workspace_root)
        .map_err(|e| format!("Cannot load site/config.ncl for pages generation: {e}"))?;

    let default_lang = site_cfg.site.default_language.clone();
    let debug_level = rustelo_utils::get_debug_level();

    let mut components_map: HashMap<String, ComponentInfo> = HashMap::new();

    for route in &site_cfg.routes {
        // Deduplicate by component name; first occurrence wins.
        if components_map.contains_key(&route.component) {
            continue;
        }

        // Representative path: prefer default language.
        let representative_path = route
            .paths
            .get(&default_lang)
            .or_else(|| route.paths.values().next())
            .cloned()
            .unwrap_or_default();

        // Infer dynamic URL params from any language variant.
        let has_slug = route.paths.values().any(|p| p.contains("{slug}"));
        let has_category = route.paths.values().any(|p| p.contains("{category}"));

        let props = route.content_type.as_ref().map(|ct| {
            let mut table = toml::map::Map::new();
            table.insert("content_type".to_string(), toml::Value::String(ct.clone()));
            toml::Value::Table(table)
        });

        let parameter_extraction = if has_slug || has_category {
            let mut table = toml::map::Map::new();
            if has_category {
                table.insert(
                    "category".to_string(),
                    toml::Value::String("path".to_string()),
                );
            }
            if has_slug {
                table.insert("slug".to_string(), toml::Value::String("path".to_string()));
            }
            Some(toml::Value::Table(table))
        } else {
            None
        };

        let route_config = RouteConfig {
            path: representative_path,
            page_component: route.component.clone(),
            language: default_lang.clone(),
            enabled: true,
            unified_component: None, // → Unified{component}Page
            lang_prefixes: None,     // → [{component_lower}-]
            props,
            content_type: route.content_type.clone(),
            module_path: None, // → component_name.to_lowercase()
            parameter_extraction,
            content_type_param: None,
        };

        let component_info = process_route_config(route_config)?;

        if debug_level >= 2 {
            println!(
                "cargo:warning=  Route → component: {} (unified: {}, exists: {})",
                component_info.component_name,
                component_info.unified_component,
                component_info.has_existing_unified
            );
        }

        components_map.insert(route.component.clone(), component_info);
    }

    if components_map.is_empty() {
        return Err(format!(
            "No routes found in configuration at {}.\n\
             Verify SITE_CONFIG_PATH points to a valid config file.",
            std::env::var("SITE_CONFIG_PATH").unwrap_or_else(|_| "<SITE_CONFIG_PATH not set>".into())
        )
        .into());
    }

    let mut components: Vec<ComponentInfo> = components_map.into_values().collect();
    components.sort_by(|a, b| a.component_name.cmp(&b.component_name));

    println!(
        "cargo:warning=Dynamically discovered {} components from site/config.ncl",
        components.len()
    );

    Ok(components)
}

/// Process route config into ComponentInfo
fn process_route_config(route: RouteConfig) -> Result<ComponentInfo, Box<dyn std::error::Error>> {
    // Extract component name from page_component
    let component_name = if route.page_component.ends_with("Page") {
        route.page_component.trim_end_matches("Page").to_string()
    } else {
        route.page_component.clone()
    };

    let unified_component = route
        .unified_component
        .clone()
        .unwrap_or_else(|| format!("Unified{}Page", component_name));

    let lang_prefixes = route.lang_prefixes.clone().unwrap_or_else(|| {
        // CamelCase → kebab-case for i18n keys: "WorkRequest" → "work-request-"
        let kebab: String = component_name
            .chars()
            .enumerate()
            .map(|(i, c)| {
                if c.is_uppercase() && i > 0 {
                    format!("-{}", c.to_lowercase())
                } else {
                    c.to_lowercase().to_string()
                }
            })
            .collect();
        vec![format!("{}-", kebab)]
    });

    let props = route.props.clone();
    let is_content_page = if let Some(toml::Value::Table(ref table)) = props {
        table.contains_key("content_type")
    } else {
        false
    };

    // Check if unified component exists (local OR foundation)
    let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
    // Workspace root is two levels up from crates/<crate-name>
    let workspace_root = Path::new(&manifest_dir).join("..").join("..");
    let component_path = if let Some(module_path) = &route.module_path {
        module_path.clone()
    } else {
        // CamelCase → snake_case: "WorkRequest" → "work_request", "Services" → "services"
        component_name
            .chars()
            .enumerate()
            .map(|(i, c)| {
                if c.is_uppercase() && i > 0 {
                    format!("_{}", c.to_lowercase())
                } else {
                    c.to_lowercase().to_string()
                }
            })
            .collect()
    };

    // Check local unified component first
    let component_dir = Path::new(&manifest_dir).join("src").join(&component_path);
    let unified_path = component_dir.join("unified.rs");
    let mod_path = component_dir.join("mod.rs");

    let has_local_unified = unified_path.exists() || mod_path.exists();

    // Check foundation unified component
    let has_foundation_unified = check_foundation_unified_exists(&unified_component, &workspace_root);

    let has_existing_unified = has_local_unified || has_foundation_unified;

    if has_foundation_unified && !has_local_unified {
        let debug_level = rustelo_utils::get_debug_level();
        if debug_level >= 2 {
            println!(
                "cargo:warning=    Foundation component available: {}",
                unified_component
            );
        }
    }

    Ok(ComponentInfo {
        component_name,
        unified_component,
        lang_prefixes,
        props,
        has_existing_unified,
        has_local_unified,
        is_content_page,
        _module_path: component_path,
        route_config: route.clone(),
    })
}

/// Check if a unified component exists in the foundation
fn check_foundation_unified_exists(unified_component: &str, workspace_root: &Path) -> bool {
    // Primary: derive path from the rustelo_pages dependency declared in Cargo.toml
    if let Some(src) = rustelo_pages_src(workspace_root) {
        if search_for_component_in_dir(&src, unified_component) {
            return true;
        }
    }

    // Secondary: cargo rustelo CLI (available when rustelo toolchain is installed)
    if let Ok(output) = std::process::Command::new("cargo")
        .args(["rustelo", "fn", "list", "-f", "page", "--format", "json"])
        .output()
    {
        if output.status.success() {
            if let Ok(stdout) = String::from_utf8(output.stdout) {
                return stdout.contains(unified_component);
            }
        }
    }

    false
}

/// Recursively search for component in directory
fn search_for_component_in_dir(dir: &Path, component_name: &str) -> bool {
    if let Ok(entries) = std::fs::read_dir(dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if path.is_file() && path.extension().is_some_and(|ext| ext == "rs") {
                if let Ok(content) = std::fs::read_to_string(&path) {
                    if content.contains(&format!("pub fn {}", component_name))
                        || content.contains(&format!("fn {}", component_name))
                    {
                        return true;
                    }
                }
            } else if path.is_dir() && search_for_component_in_dir(&path, component_name) {
                return true;
            }
        }
    }
    false
}

/// Copy generated file to reviewable location using existing cache system
fn copy_to_reviewable_location(
    filename: &str,
    out_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    let source_file = out_path.join(filename);

    // Use existing cache build path from manifest + pages subdirectory
    let review_dir = rustelo_utils::cache_build_path().join("pages");

    // Ensure directory exists
    fs::create_dir_all(&review_dir)?;

    let dest_file = review_dir.join(filename);

    if source_file.exists() {
        fs::copy(&source_file, &dest_file)?;
        let debug_level = rustelo_utils::get_debug_level();
        if debug_level >= 2 {
            println!(
                "cargo:warning=📄 Copied {} to cache: {}",
                filename,
                dest_file.display()
            );
        }
    }

    Ok(())
}

/// Clean the generated output directory
fn clean_generated_output(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
    if out_dir.exists() {
        // Remove all page_*.rs files from previous builds
        for entry in fs::read_dir(out_dir)? {
            let entry = entry?;
            let path = entry.path();
            if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) {
                if file_name.starts_with("page_") && file_name.ends_with(".rs") {
                    fs::remove_file(&path)?;
                }
            }
        }
    }
    Ok(())
}
