//! Server build script - Uses Rustelo Foundation tools
//!
//! This build script uses rustelo_tools for all generation instead of
//! custom implementations, following the PAP-compliant approach.

use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
// Note: HashMap is imported locally within functions (generate_resource_contributor, etc.)
// not used at module level, so keeping it local to avoid confusion
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

/// Server configuration structure
#[derive(Deserialize, Serialize, Debug)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub workers: Option<usize>,
    pub content_root: String,
    pub public_path: String,
}

/// Smart cache for server generation
struct ServerCache {
    cache_dir: PathBuf,
}

impl ServerCache {
    fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let manifest = rustelo_utils::ManifestResolver::load()?;
        let cache_dir = manifest
            .root()
            .join("target/site_build/devtools/build-cache/server");
        fs::create_dir_all(&cache_dir)?;
        Ok(Self { cache_dir })
    }

    fn get_content_hash(
        &self,
        routes_dir: &Path,
        components_dir: Option<&Path>,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let mut hasher = Sha256::new();

        // Hash all route files
        if routes_dir.exists() {
            let mut route_files: Vec<_> = fs::read_dir(routes_dir)?
                .filter_map(|entry| entry.ok())
                .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "toml"))
                .collect();
            route_files.sort_by_key(|entry| entry.path());

            for entry in route_files {
                let content = fs::read_to_string(entry.path())?;
                hasher.update(entry.path().to_string_lossy().as_bytes());
                hasher.update(content.as_bytes());
            }
        }

        // Hash component files if provided
        if let Some(comp_dir) = components_dir {
            if comp_dir.exists() {
                let mut comp_files: Vec<_> = std::fs::read_dir(comp_dir)?
                    .filter_map(|entry| entry.ok())
                    .filter(|entry| entry.path().extension().is_some_and(|ext| ext == "rs"))
                    .collect();
                comp_files.sort_by_key(|entry| entry.path());

                for entry in comp_files {
                    let content = fs::read_to_string(entry.path())?;
                    hasher.update(entry.path().to_string_lossy().as_bytes());
                    hasher.update(content.as_bytes());
                }
            }
        }

        let hash = hasher.finalize();
        Ok(hash.iter().fold(String::new(), |mut s, b| {
            use std::fmt::Write;
            let _ = write!(s, "{b:02x}");
            s
        })[..16]
            .to_string())
    }

    #[allow(dead_code)]
    fn get_cache_status(&self, cache_key: &str, cache_type: &str) -> String {
        let cache_file = self
            .cache_dir
            .join(format!("{}_{}.cache", cache_type, cache_key));
        if cache_file.exists() {
            "CACHE HIT".to_string()
        } else {
            "CACHE MISS".to_string()
        }
    }

    fn is_cached(&self, cache_key: &str, cache_type: &str) -> bool {
        let cache_file = self
            .cache_dir
            .join(format!("{}_{}.cache", cache_type, cache_key));
        cache_file.exists()
    }

    fn save_cache(
        &self,
        cache_key: &str,
        cache_type: &str,
        content: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let cache_file = self
            .cache_dir
            .join(format!("{}_{}.cache", cache_type, cache_key));
        fs::write(cache_file, content)?;
        Ok(())
    }

    fn read_cache(
        &self,
        cache_key: &str,
        cache_type: &str,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let cache_file = self
            .cache_dir
            .join(format!("{}_{}.cache", cache_type, cache_key));
        Ok(fs::read_to_string(cache_file)?)
    }
}

/// Walk `site/i18n/locales/` and collect all FTL file contents keyed as
/// `{lang}_{path_underscore}` (e.g. `en_pages_home`).  This matches the key
/// format expected by `get_parsed_ftl_for_language`.  Replaces the legacy
/// `build_config::load_config()` approach that required `config.dev.toml`.
fn load_ftl_from_locales(project_root: &Path) -> std::collections::HashMap<String, String> {
    let locales_dir = project_root.join("site").join("i18n").join("locales");
    let mut ftl_files = std::collections::HashMap::new();

    if !locales_dir.exists() {
        println!(
            "cargo:warning=  ⚠️  FTL locales dir not found: {}",
            locales_dir.display()
        );
        return ftl_files;
    }

    let lang_entries = match std::fs::read_dir(&locales_dir) {
        Ok(r) => r,
        Err(e) => {
            println!("cargo:warning=  ⚠️  Cannot read FTL locales dir: {}", e);
            return ftl_files;
        }
    };

    for lang_entry in lang_entries.filter_map(|e| e.ok()) {
        let lang_path = lang_entry.path();
        if !lang_path.is_dir() {
            continue;
        }
        let lang = match lang_path.file_name().and_then(|n| n.to_str()) {
            Some(l) => l.to_string(),
            None => continue,
        };

        // BFS walk to collect all .ftl files under this language directory.
        let mut stack = vec![lang_path.clone()];
        while let Some(dir) = stack.pop() {
            let entries = match std::fs::read_dir(&dir) {
                Ok(e) => e,
                Err(_) => continue,
            };
            for entry in entries.filter_map(|e| e.ok()) {
                let path = entry.path();
                if path.is_dir() {
                    stack.push(path);
                } else if path.extension().is_some_and(|x| x == "ftl") {
                    let rel = path.strip_prefix(&lang_path).unwrap_or(&path);
                    let key_suffix = rel
                        .with_extension("")
                        .components()
                        .filter_map(|c| {
                            if let std::path::Component::Normal(n) = c {
                                n.to_str().map(|s| s.to_string())
                            } else {
                                None
                            }
                        })
                        .collect::<Vec<_>>()
                        .join("_");
                    let key = format!("{}_{}", lang, key_suffix);
                    match std::fs::read_to_string(&path) {
                        Ok(content) => {
                            println!(
                                "cargo:warning=  ✓ Loaded FTL: {} ({} bytes)",
                                key,
                                content.len()
                            );
                            println!("cargo:rerun-if-changed={}", path.display());
                            ftl_files.insert(key, content);
                        }
                        Err(e) => {
                            println!("cargo:warning=  ⚠️  Cannot read {}: {}", path.display(), e);
                        }
                    }
                }
            }
        }
    }

    ftl_files
}

/// Generate ResourceContributor implementation for menus, footers, themes, and FTL files
fn generate_resource_contributor(
    config_path: &Path,
    out_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    use std::collections::HashMap;

    println!("cargo:warning=🔨 Generating ResourceContributor implementation");
    println!("cargo:warning=  Config path: site/config");

    // Load menus from site/config/index.ncl — strict, no fallback
    let mut menus = HashMap::new();

    let manifest_root = rustelo_utils::ManifestResolver::load()
        .map(|m| m.root().to_path_buf())
        .unwrap_or_else(|_| config_path.parent().unwrap_or(config_path).to_path_buf());

    let site_cfg = build_config::site_config::load_unified_site_config(&manifest_root)
        .unwrap_or_else(|e| {
            panic!(
                "Cannot load site/config/index.ncl for menu generation: {e}\n\
             Install Nickel: cargo install nickel-lang-cli"
            )
        });

    let rbac_deny_patterns =
        build_config::rbac_config::load_rbac_anonymous_deny_patterns(&manifest_root)
            .unwrap_or_default();

    let menu_toml = site_cfg.menu_toml_unified();
    let len = menu_toml.len();
    menus.insert("main".to_string(), menu_toml);
    println!(
        "cargo:warning=  ✓ Loaded unified menu from site/config/index.ncl ({} bytes)",
        len
    );

    // Also insert per-language header-zone entries so get_menu("en"/"es") resolves
    // directly from MENU_REGISTRY, bypassing the filesystem fallback (menus/en.toml).
    // auth_required is derived from site/rbac.ncl — single source of truth.
    // This ensures server and WASM render identical item counts → hydration parity.
    for lang in site_cfg.languages() {
        let lang_toml = site_cfg.menu_toml_for_lang_with_rbac("header", lang, &rbac_deny_patterns);
        let lang_len = lang_toml.len();
        menus.insert(lang.to_string(), lang_toml);
        println!(
            "cargo:warning=  ✓ Loaded per-language menu for '{}' ({} bytes)",
            lang, lang_len
        );
    }

    // Load footers from site/config/index.ncl — strict, no fallback
    // site_cfg already loaded above; generate per-language TOML matching FooterConfig's shape.
    let mut footers = HashMap::new();
    {
        use serde::Serialize;

        #[derive(Serialize)]
        struct FooterToml {
            title: String,
            #[serde(rename = "company-desc")]
            company_desc: String,
            copyright_text: String,
            #[serde(skip_serializing_if = "Vec::is_empty")]
            social_links: Vec<SocialLinkToml>,
            #[serde(skip_serializing_if = "Vec::is_empty")]
            menu: Vec<MenuItemToml>,
        }

        #[derive(Serialize)]
        struct SocialLinkToml {
            name: String,
            url: String,
            icon_class: String,
        }

        #[derive(Serialize)]
        struct MenuItemToml {
            route: String,
            label: HashMap<String, String>,
        }

        let footer = &site_cfg.footer;
        for lang in site_cfg.languages() {
            let company_desc = footer
                .company_desc
                .get(lang.as_str())
                .cloned()
                .unwrap_or_default();
            let copyright_text = footer
                .copyright_text
                .get(lang.as_str())
                .cloned()
                .unwrap_or_default();
            let footer_menu_items = site_cfg.menu_items_for_zone("footer", lang);

            let footer_data = FooterToml {
                title: footer.title.clone(),
                company_desc,
                copyright_text,
                social_links: footer
                    .social_links
                    .iter()
                    .map(|l| SocialLinkToml {
                        name: l.name.clone(),
                        url: l.url.clone(),
                        icon_class: l.icon.clone(),
                    })
                    .collect(),
                menu: footer_menu_items
                    .iter()
                    .map(|item| MenuItemToml {
                        route: item.route.clone(),
                        label: {
                            let mut m = HashMap::new();
                            m.insert(lang.to_string(), item.label.clone());
                            m
                        },
                    })
                    .collect(),
            };

            let toml_str = toml::to_string(&footer_data)?;
            let len = toml_str.len();
            footers.insert(lang.to_string(), toml_str);
            println!("cargo:warning=  ✓ Generated footer for '{lang}' ({len} bytes, from site/config/index.ncl)");
        }
    }

    // Load themes from config/themes/*.toml
    let mut themes = HashMap::new();
    let themes_dir = config_path.join("themes");
    println!("cargo:warning=  Looking for themes in: site/config/themes");
    if themes_dir.exists() {
        for entry in fs::read_dir(&themes_dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.extension().is_some_and(|ext| ext == "toml") {
                if let Some(filename) = path.file_stem().and_then(|s| s.to_str()) {
                    let content = fs::read_to_string(&path)?;
                    let content_len = content.len();
                    themes.insert(filename.to_string(), content);
                    println!(
                        "cargo:warning=  ✓ Loaded theme: {} ({} bytes)",
                        filename, content_len
                    );
                }
            }
        }
    }

    // Generate per-language route TOML from site/config/index.ncl — strict, no fallback
    // Uses site_cfg already loaded above.  RoutesConfig is the same struct that
    // routing/config.rs deserializes at runtime, so the format is guaranteed compatible.
    let mut routes = HashMap::new();
    {
        use build_config::site_config::RoutesConfig;

        let expanded = site_cfg.routes_expanded(); // HashMap<lang, Vec<RouteConfigToml>>
        for (lang, route_vec) in expanded {
            let cfg = RoutesConfig { routes: route_vec };
            match toml::to_string(&cfg) {
                Ok(toml_str) => {
                    let len = toml_str.len();
                    println!(
                        "cargo:warning=  ✓ Generated routes for '{lang}' \
                         ({len} bytes, from site/config/index.ncl)"
                    );
                    routes.insert(lang, toml_str);
                }
                Err(e) => {
                    println!("cargo:warning=  ⚠️  Failed to serialize routes for '{lang}': {e}");
                }
            }
        }
    }

    // Load FTL files directly from site/i18n/locales/ using manifest_root.
    // build_config::load_config() required config.dev.toml which no longer exists
    // after the modular NCL migration — replaced with direct filesystem walk.
    println!("cargo:warning=  Looking for FTL files in: site/i18n/locales");
    let ftl_files = load_ftl_from_locales(&manifest_root);

    // Delegate code generation to rustelo_tools
    rustelo_tools::build::generate_resource_contributor_code(
        "WebsiteResourceContributor",
        &menus,
        &footers,
        &themes,
        &ftl_files,
        &routes,
        out_dir,
    )?;

    // Generate server_theme_constants.rs — same pattern as client's theme_constants.rs
    // Single source of truth: site/config/index.ncl [logo]
    let logo = &site_cfg.logo;
    let width_const = match &logo.width {
        Some(w) => format!("Some(\"{}\")", w),
        None => "None".to_string(),
    };
    let height_const = match &logo.height {
        Some(h) => format!("Some(\"{}\")", h),
        None => "None".to_string(),
    };
    let server_theme = format!(
        r#"/// Theme configuration constants for SSR server
/// Generated at build time from site/config/index.ncl [logo]
#[allow(clippy::module_inception)]
pub mod theme {{
    pub const DEFAULT_THEME_NAME: &str = "default";

    /// Logo configuration — single source of truth: site/config/index.ncl
    pub mod logo {{
        pub const LIGHT_SRC: &str = "{}";
        pub const DARK_SRC: &str = "{}";
        pub const ALT: &str = "{}";
        pub const SHOW_IN_NAV: bool = {};
        pub const SHOW_IN_FOOTER: bool = {};
        pub const WIDTH: Option<&str> = {};
        pub const HEIGHT: Option<&str> = {};
    }}
}}
"#,
        logo.light_src,
        logo.dark_src,
        logo.alt,
        logo.show_in_nav,
        logo.show_in_footer,
        width_const,
        height_const
    );
    let server_theme_file = out_dir.join("server_theme_constants.rs");
    fs::write(&server_theme_file, server_theme)?;
    println!(
        "cargo:warning=  ✓ Logo config generated from site/config/index.ncl → server_theme_constants.rs"
    );

    Ok(())
}

/// Emit three scope-specific aggregator files consumed by server/src/lib.rs.
///
/// `include!()` expands into the surrounding item context — so top-level,
/// `pub mod generated {}`, and `pub mod config_constants {}` each need their
/// own aggregator file; a single flat file cannot span module scopes.
fn generate_server_includes_aggregator(
    top_level: &[&str],
    generated_mod: &[&str],
    config_mod: &[&str],
    out_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let header = "// Auto-generated aggregator — do not edit directly.\n";

    let write_scope = |names: &[&str], filename: &str| -> Result<(), Box<dyn std::error::Error>> {
        let mut content = header.to_string();
        for name in names {
            content.push_str(&format!(
                "include!(concat!(env!(\"OUT_DIR\"), \"/{}\"));\n",
                name
            ));
        }
        std::fs::write(std::path::Path::new(out_dir).join(filename), content)?;
        Ok(())
    };

    write_scope(top_level, "server_top_includes.rs")?;
    write_scope(generated_mod, "server_generated_mod_includes.rs")?;
    write_scope(config_mod, "server_config_mod_includes.rs")?;
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Use new PAP-compliant manifest system
    let manifest = rustelo_utils::get_manifest()?;
    let content_path = rustelo_utils::manifest_content_path();
    let config_path = rustelo_utils::manifest_config_path();

    println!("cargo:rerun-if-changed={}/config", content_path.display());
    println!("cargo:rerun-if-changed={}", config_path.display());
    println!("cargo:rerun-if-changed=src/");
    println!("cargo:rerun-if-changed={}/content", content_path.display());
    println!("cargo:rerun-if-changed={}", content_path.display());

    let out_dir = env::var("OUT_DIR")?;

    // config_constants module: NCL-sourced files (local generator — owns its artifact names)
    let ncl_consts = generate_config_constants()?;

    // ResourceContributor (not included in lib.rs — build artifact only)
    generate_resource_contributor(&config_path, Path::new(&out_dir))?;

    // Initialize cache for server builds
    let cache = ServerCache::new()?;

    // Top-level: server config + shell asset paths
    let server_cfg = generate_server_config()?;
    let shell_assets = generate_shell_asset_paths(manifest)?;

    // config_constants module: env-var helpers (external tool — filename declared here as the
    // single point of knowledge between the call site and the aggregator)
    const TOOLS_CONFIG: &str = "config_constants.rs";
    rustelo_tools::build::config_constants::generate_config_constants(&out_dir)?;

    // config_constants module: content types from site/config/index.ncl [content_types]
    let content_types_const = generate_content_types_constants(manifest)?;

    // generated module: RouteComponent enum from site/config/index.ncl
    let route_handlers = generate_route_handlers_with_tools()?;

    // resource_registry.rs path notification (build artifact only, not included in lib.rs)
    let resource_registry_path = Path::new(&out_dir).join("resource_registry.rs");
    if resource_registry_path.exists() {
        println!(
            "cargo:rustc-env=RUSTELO_RESOURCE_REGISTRY_PATH={}",
            resource_registry_path.display()
        );
        println!("cargo:warning=✅ Set RUSTELO_RESOURCE_REGISTRY_PATH for rustelo_core_lib");
    }

    // route_table.rs (not included in lib.rs — build artifact only)
    {
        let manifest_dir = manifest.root();
        let site_cfg = build_config::site_config::load_unified_site_config(manifest_dir)
            .unwrap_or_else(|e| {
                panic!(
                    "Cannot load site/config/index.ncl for route table: {e}\n\
                     Install Nickel: cargo install nickel-lang-cli"
                )
            });
        let mut routes = std::collections::BTreeMap::new();
        for (_lang, route_list) in site_cfg.routes_expanded() {
            for route in route_list {
                if route.path.starts_with('/') {
                    routes.insert(route.path, route.component);
                }
            }
        }
        println!(
            "cargo:warning=Server: Route table built from site/config/index.ncl ({} entries)",
            routes.len()
        );
        rustelo_tools::build::generate_route_table(&routes, Path::new(&out_dir))?;
    }

    // Top-level: server routing (page dispatch + full-path OnceLock lookup)
    const SERVER_ROUTING: &str = "server_routing.rs";
    {
        let manifest_dir = manifest.root();
        let cache_build_path = manifest.manifest().build.cache_build_path.clone();
        let pages_cache_dir = manifest_dir
            .join("target")
            .join(&cache_build_path)
            .join("pages");
        let crate_relative = format!("../../target/{}/pages", cache_build_path);
        let site_cfg = build_config::site_config::load_unified_site_config(manifest_dir)
            .unwrap_or_else(|e| {
                panic!(
                    "Cannot load site/config/index.ncl for page provider: {e}\n\
                     Install Nickel: cargo install nickel-lang-cli"
                )
            });
        let mut route_mappings = std::collections::HashMap::new();
        let mut path_to_content_type = std::collections::HashMap::new();
        for (_lang, route_list) in site_cfg.routes_expanded() {
            for route in route_list {
                if !route.path.starts_with('/') {
                    continue;
                }
                route_mappings
                    .entry(route.path.clone())
                    .or_insert_with(|| route.component.clone());
                if let Some(ct) = route.content_type {
                    path_to_content_type.entry(route.path).or_insert(ct);
                }
            }
        }
        println!(
            "cargo:warning=Server: Loaded {} route mappings from configuration",
            route_mappings.len()
        );
        rustelo_tools::build::generate_server_routing(
            &pages_cache_dir,
            &crate_relative,
            &route_mappings,
            &path_to_content_type,
            Path::new(&out_dir),
        )?;
    }
    let server_routing = SERVER_ROUTING;

    generate_server_documentation(manifest, &cache)?;
    create_code_review_copy()?;

    // Emit the 3 scope-specific aggregator files consumed by server/src/lib.rs.
    // Ordering within each scope matches the original include!() order in lib.rs.
    generate_server_includes_aggregator(
        &[server_cfg, shell_assets, server_routing],
        &[route_handlers],
        &[TOOLS_CONFIG, ncl_consts[0], ncl_consts[1], content_types_const],
        &out_dir,
    )?;

    println!("cargo:warning=Server build completed (tools + server-specific)");
    Ok(())
}


/// Convert a component name like "HomePage" to its i18n key prefix "homepage-page".
fn component_to_key_prefix(component: &str) -> String {
    format!("{}-page", component.to_lowercase())
}

/// Generate `generated_routes.rs` directly from site/config/index.ncl.
///
/// Replaces the `rustelo_tools::generate_route_components` call which required
/// `site/config/routes/*.toml` to exist on disk. With NCL as the single source
/// of truth, we derive the `RouteComponent` enum from the already-loaded config.
fn generate_route_handlers_with_tools() -> Result<&'static str, Box<dyn std::error::Error>> {
    const OUTPUT: &str = "generated_routes.rs";
    println!("cargo:warning=🚀 Generating RouteComponent enum from site/config/index.ncl");

    let out_dir = env::var("OUT_DIR")?;
    let manifest = rustelo_utils::ManifestResolver::load()?;
    let manifest_dir = manifest.root();

    let site_cfg = build_config::site_config::load_unified_site_config(manifest_dir)
        .unwrap_or_else(|e| {
            panic!(
                "Cannot load site/config/index.ncl for route components: {e}\n\
                 Install Nickel: cargo install nickel-lang-cli"
            )
        });

    // Collect unique component names in deterministic order.
    // NotFound is always required as the wildcard arm.
    let mut components: std::collections::BTreeSet<String> = site_cfg
        .routes
        .iter()
        .map(|r| r.component.clone())
        .collect();
    components.insert("NotFound".to_string());

    let output_path = std::path::Path::new(&out_dir).join(OUTPUT);
    let mut code = String::new();

    code.push_str("// Auto-generated route component definitions\n");
    code.push_str("// Regenerated at build time from site/config/index.ncl — do not edit\n\n");
    code.push_str("use serde::{Deserialize, Serialize};\n\n");

    code.push_str("#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]\n");
    code.push_str("pub enum RouteComponent {\n");
    for c in &components {
        code.push_str(&format!("    {},\n", c));
    }
    code.push_str("}\n\n");

    code.push_str("impl RouteComponent {\n");

    // from_str (inherent, mirrors trait below)
    code.push_str("    #[allow(clippy::should_implement_trait)]\n");
    code.push_str("    pub fn from_str(component: &str) -> Self {\n");
    code.push_str("        match component {\n");
    for c in &components {
        if c != "NotFound" {
            code.push_str(&format!(
                "            \"{}\" => RouteComponent::{},\n",
                c, c
            ));
        }
    }
    code.push_str("            _ => RouteComponent::NotFound,\n");
    code.push_str("        }\n    }\n\n");

    // as_str
    code.push_str("    pub fn as_str(&self) -> &'static str {\n");
    code.push_str("        match self {\n");
    for c in &components {
        code.push_str(&format!(
            "            RouteComponent::{} => \"{}\",\n",
            c, c
        ));
    }
    code.push_str("        }\n    }\n\n");

    // title_key / description_key / keywords_key
    for suffix in &["title", "description", "keywords"] {
        code.push_str(&format!(
            "    pub fn {}_key(&self) -> &'static str {{\n        match self {{\n",
            suffix
        ));
        for c in &components {
            code.push_str(&format!(
                "            RouteComponent::{} => \"{}-{}\",\n",
                c,
                component_to_key_prefix(c),
                suffix
            ));
        }
        code.push_str("        }\n    }\n\n");
    }

    // is_multilingual
    code.push_str(
        "    pub fn is_multilingual(&self) -> bool {\n        \
         !matches!(self, RouteComponent::NotFound)\n    }\n}\n\n",
    );

    // std::str::FromStr
    code.push_str("impl std::str::FromStr for RouteComponent {\n");
    code.push_str("    type Err = String;\n");
    code.push_str("    fn from_str(s: &str) -> Result<Self, Self::Err> {\n");
    code.push_str("        let component = match s {\n");
    for c in &components {
        if c != "NotFound" {
            code.push_str(&format!(
                "            \"{}\" => RouteComponent::{},\n",
                c, c
            ));
        }
    }
    code.push_str("            _ => return Err(format!(\"Unknown route component: {}\", s)),\n");
    code.push_str("        };\n        Ok(component)\n    }\n}\n\n");

    code.push_str("// Trait-based rendering — no macro generation needed\n");

    fs::write(&output_path, &code)?;

    println!(
        "cargo:warning=✅ RouteComponent enum written to generated_routes.rs ({} variants from site/config/index.ncl)",
        components.len()
    );

    Ok(OUTPUT)
}


/// Generate `shell_asset_paths.rs` — CSS/JS path slices from `site/config/assets.ncl`.
///
/// Produces `source_mode::{CSS, JS}` and `bundled_mode::{CSS, JS}` static slices
/// consumed by `shell.rs`. Adding or removing stylesheets requires only editing
/// `site/config/assets.ncl` — no Rust changes needed.
fn generate_shell_asset_paths(
    manifest_resolver: &rustelo_utils::ManifestResolver,
) -> Result<&'static str, Box<dyn std::error::Error>> {
    const OUTPUT: &str = "shell_asset_paths.rs";

    let site_cfg = build_config::site_config::load_unified_site_config(manifest_resolver.root())
        .unwrap_or_else(|e| {
            panic!(
                "Cannot load site/config/index.ncl for shell assets: {e}\n\
                 Install Nickel: cargo install nickel-lang-cli"
            )
        });

    let fmt_slice = |paths: &[String]| -> String {
        paths
            .iter()
            .map(|p| format!("    \"{}\",\n", p))
            .collect::<String>()
    };

    let code = format!(
        r#"// Auto-generated shell asset paths — sourced from site/config/assets.ncl
// DO NOT EDIT — regenerated on every build.

pub mod source_mode {{
    pub static CSS: &[&str] = &[
{src_css}    ];
    pub static JS: &[&str] = &[
{src_js}    ];
}}

pub mod bundled_mode {{
    pub static CSS: &[&str] = &[
{bnd_css}    ];
    pub static JS: &[&str] = &[
{bnd_js}    ];
}}

pub mod htmx_mode {{
    pub static CSS: &[&str] = &[
{htmx_css}    ];
}}
"#,
        src_css = fmt_slice(&site_cfg.assets.source_css),
        src_js = fmt_slice(&site_cfg.assets.source_js),
        bnd_css = fmt_slice(&site_cfg.assets.bundled_css),
        bnd_js = fmt_slice(&site_cfg.assets.bundled_js),
        htmx_css = fmt_slice(&site_cfg.assets.htmx_css),
    );

    let out_dir = env::var("OUT_DIR")?;
    fs::write(Path::new(&out_dir).join(OUTPUT), code)?;
    println!("cargo:rerun-if-changed=site/config/assets.ncl");
    println!("cargo:warning=✅ Shell asset paths written to OUT_DIR/shell_asset_paths.rs");
    Ok(OUTPUT)
}

fn generate_server_config() -> Result<&'static str, Box<dyn std::error::Error>> {
    const OUTPUT: &str = "server_config.rs";
    let out_dir = env::var("OUT_DIR")?;
    let server_config_content = r#"
// Auto-generated server configuration
// This is a default configuration to prevent build errors

pub const DEFAULT_HOST: &str = "127.0.0.1";
pub const DEFAULT_PORT: u16 = 3030;
pub const DEFAULT_WORKERS: usize = 4;

#[derive(Debug, Clone)]
pub struct ServerConfigData {
    pub host: String,
    pub port: u16,
    pub workers: usize,
    pub content_root: String,
    pub public_path: String,
}

impl Default for ServerConfigData {
    fn default() -> Self {
        Self {
            host: DEFAULT_HOST.to_string(),
            port: DEFAULT_PORT,
            workers: DEFAULT_WORKERS,
            content_root: "../site".to_string(),
            public_path: "public".to_string(),
        }
    }
}

pub fn get_server_config() -> ServerConfigData {
    ServerConfigData::default()
}
"#;

    let server_config_file = Path::new(&out_dir).join(OUTPUT);
    fs::write(server_config_file, server_config_content)?;
    Ok(OUTPUT)
}

fn generate_config_constants() -> Result<[&'static str; 2], Box<dyn std::error::Error>> {
    const NCL_DB: &str = "ncl_database_constants.rs";
    const NCL_PATHS: &str = "ncl_path_constants.rs";
    let out_dir = env::var("OUT_DIR")?;

    // Load NCL database config — non-fatal if NCL is unavailable (e.g. CI without nickel).
    let config_path = env::var("CARGO_MANIFEST_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| PathBuf::from("."));
    let manifest_root = rustelo_utils::ManifestResolver::load()
        .map(|m| m.root().to_path_buf())
        .unwrap_or_else(|_| {
            config_path
                .parent()
                .unwrap_or(config_path.as_path())
                .to_path_buf()
        });

    let ncl_cfg = build_config::site_config::load_unified_site_config(&manifest_root).ok();
    let ncl_db = ncl_cfg.as_ref().map(|cfg| &cfg.database);

    let ncl_db_url = ncl_db
        .map(|d| d.url.as_str())
        .unwrap_or("sqlite:data/dev_database.db");
    let ncl_db_create = ncl_db.map(|d| d.create_if_missing).unwrap_or(true);
    let ncl_db_max_conn = ncl_db.map(|d| d.max_connections).unwrap_or(5);
    let ncl_db_min_conn = ncl_db.map(|d| d.min_connections).unwrap_or(1);
    let ncl_db_connect_timeout = ncl_db.map(|d| d.connect_timeout).unwrap_or(30);
    let ncl_db_idle_timeout = ncl_db.map(|d| d.idle_timeout).unwrap_or(600);
    let ncl_db_max_lifetime = ncl_db.map(|d| d.max_lifetime).unwrap_or(1800);

    // Write NCL database constants to a separate file.
    // config_constants.rs (env-var helpers) is generated separately via
    // rustelo_tools::build::config_constants::generate_config_constants().
    let ncl_db_content = format!(
        r#"// Auto-generated NCL database constants — sourced from site/config/index.ncl [database]
// DO NOT EDIT — regenerated on every build.
// .env DATABASE_URL takes priority over NCL_DATABASE_URL at runtime.
pub const NCL_DATABASE_URL: &str = "{}";
pub const NCL_DATABASE_CREATE_IF_MISSING: bool = {};
pub const NCL_DATABASE_MAX_CONNECTIONS: u32 = {};
pub const NCL_DATABASE_MIN_CONNECTIONS: u32 = {};
pub const NCL_DATABASE_CONNECT_TIMEOUT: u64 = {};
pub const NCL_DATABASE_IDLE_TIMEOUT: u64 = {};
pub const NCL_DATABASE_MAX_LIFETIME: u64 = {};
"#,
        ncl_db_url,
        ncl_db_create,
        ncl_db_max_conn,
        ncl_db_min_conn,
        ncl_db_connect_timeout,
        ncl_db_idle_timeout,
        ncl_db_max_lifetime,
    );

    let ncl_db_file = Path::new(&out_dir).join(NCL_DB);
    fs::write(&ncl_db_file, ncl_db_content)?;
    println!("cargo:warning=✅ NCL database constants written to OUT_DIR/ncl_database_constants.rs (url={ncl_db_url})");

    // Write NCL path constants — sourced from site/config/index.ncl [paths].
    let ncl_paths = ncl_cfg.as_ref().map(|cfg| &cfg.paths);
    let migrations_path = ncl_paths
        .and_then(|p| p.migrations.as_deref())
        .unwrap_or("rustelo/migrations");
    let email_templates_path = ncl_paths
        .and_then(|p| p.email_templates.as_deref())
        .unwrap_or("email_templates");

    let ncl_paths_content = format!(
        r#"// Auto-generated NCL path constants — sourced from site/config/index.ncl [paths]
// DO NOT EDIT — regenerated on every build.
// Values are project-root-relative paths as declared in config.ncl.
pub const SITE_MIGRATIONS_PATH: &str = "{}";
pub const SITE_EMAIL_TEMPLATES_PATH: &str = "{}";
"#,
        migrations_path, email_templates_path,
    );

    let ncl_paths_file = Path::new(&out_dir).join(NCL_PATHS);
    fs::write(&ncl_paths_file, ncl_paths_content)?;
    println!("cargo:rerun-if-changed=site/config/index.ncl");
    println!("cargo:rerun-if-changed=site/rbac.ncl");
    println!(
        "cargo:warning=✅ NCL path constants written \
         (migrations={migrations_path}, email_templates={email_templates_path})"
    );

    Ok([NCL_DB, NCL_PATHS])
}

/// Generate content type constants from site/config/index.ncl [content_types].
///
/// Produces `pub const CONTENT_TYPES: &[(&str, &str)]` included in the
/// `config_constants` module.  At runtime, `resources::register_content_kinds()`
/// iterates the slice and builds `ContentConfig` entries — no TOML I/O needed.
fn generate_content_types_constants(
    manifest_resolver: &rustelo_utils::ManifestResolver,
) -> Result<&'static str, Box<dyn std::error::Error>> {
    const OUTPUT: &str = "ncl_content_types.rs";
    let out_dir = env::var("OUT_DIR")?;

    let site_cfg =
        build_config::site_config::load_unified_site_config(manifest_resolver.root())?;

    let entries: String = site_cfg
        .content_types
        .iter()
        .filter(|ct| ct.enabled)
        .map(|ct| format!("    (\"{}\", \"{}\"),\n", ct.name, ct.directory))
        .collect();

    let content = format!(
        r#"// Auto-generated — sourced from site/config/index.ncl [content_types]. DO NOT EDIT.
/// Enabled content types as (name, directory) pairs.
/// Consumed by `resources::register_content_kinds()` at server startup.
pub const CONTENT_TYPES: &[(&str, &str)] = &[
{}];"#,
        entries
    );

    fs::write(Path::new(&out_dir).join(OUTPUT), content)?;
    println!("cargo:warning=Generated content type constants ({} types)", site_cfg.content_types.iter().filter(|ct| ct.enabled).count());
    Ok(OUTPUT)
}

/// Generate server documentation using rustelo_tools
fn generate_server_documentation(
    manifest: &rustelo_utils::ManifestResolver,
    _cache: &ServerCache,
) -> Result<(), Box<dyn std::error::Error>> {
    // Create info directory
    let info_path = manifest.root().join("site/info");
    std::fs::create_dir_all(&info_path)?;

    // Use rustelo_tools to generate comprehensive server analysis
    let project_root = manifest.root().to_string_lossy();

    // Try to generate server documentation, but handle the array conversion error gracefully
    if let Err(e) = rustelo_tools::comprehensive_analysis::generate_server_documentation(
        &project_root,
        &info_path.to_string_lossy(),
    ) {
        // Log the specific error for debugging but continue the build
        println!(
            "cargo:warning=Server documentation generation failed ({}), continuing build",
            e
        );

        // Create a basic server analysis file as fallback
        let server_analysis_path = info_path.join("server_analysis.md");
        let basic_analysis = format!(
            "# Server Analysis\n\n\
            Generated at: {}\n\
            Project root: {}\n\n\
            ## Build Status\n\
            - Route metadata: ✅ Generated\n\
            - Server config: ✅ Generated\n\
            - Comprehensive analysis: ❌ Failed due to array conversion\n\n\
            ## Notes\n\
            The comprehensive server analysis failed due to TOML array conversion issues.\n\
            Route metadata and basic server configuration were generated successfully.\n",
            chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
            project_root
        );
        std::fs::write(server_analysis_path, basic_analysis)?;
    } else {
        println!("cargo:warning=Server documentation generated successfully");
    }

    // Generate route metadata in data format similar to p-website
    generate_route_metadata(manifest)?;

    println!("cargo:warning=Generated server documentation in site/info/");
    Ok(())
}

/// Generate route metadata (similar to p-website's data.toml)
fn generate_route_metadata(
    manifest: &rustelo_utils::ManifestResolver,
) -> Result<(), Box<dyn std::error::Error>> {
    let routes_dir = rustelo_utils::routes_path();
    let info_path = manifest.root().join("site/info");

    // Initialize cache for route metadata
    let cache = ServerCache::new()?;
    let components_dir = manifest.root().join("crates/client/src/components");
    let cache_key = cache.get_content_hash(&routes_dir, Some(&components_dir))?;
    let cache_status = if cache.is_cached(&cache_key, "metadata_json")
        && cache.is_cached(&cache_key, "metadata_toml")
    {
        "CACHE HIT".to_string()
    } else {
        "CACHE MISS".to_string()
    };

    println!(
        "cargo:warning=ServerCache metadata: {} (hash: {})",
        cache_status, cache_key
    );

    // Try to use cached metadata
    if cache.is_cached(&cache_key, "metadata_json") && cache.is_cached(&cache_key, "metadata_toml")
    {
        let cached_json = cache.read_cache(&cache_key, "metadata_json")?;
        let cached_toml = cache.read_cache(&cache_key, "metadata_toml")?;

        fs::create_dir_all(&info_path)?;
        fs::write(info_path.join("data.json"), cached_json)?;
        fs::write(info_path.join("data.toml"), cached_toml)?;

        println!("cargo:warning=✅ Using cached route metadata (CACHE HIT)");
        return Ok(());
    }

    println!("cargo:warning=🔄 Generating route metadata (CACHE MISS)");

    let mut route_data = serde_json::json!({
        "page_routes": [],
        "api_routes": [],
        "components": [],
        "generated_at": chrono::Utc::now().to_rfc3339()
    });

    if routes_dir.exists() {
        // Use typed route loading (NCL-first, TOML-fallback)
        match build_config::route_config::load_all_routes(&routes_dir) {
            Ok(routes_by_lang) => {
                for (lang, routes) in routes_by_lang {
                    for route in routes {
                        // Only include internal routes
                        if !route.is_external.unwrap_or(false) && route.path.starts_with('/') {
                            let route_info = serde_json::json!({
                                "path": route.path,
                                "component": route.component,
                                "language": lang,
                                "enabled": route.enabled,
                                "handler": format!("handle_{}_{}", lang, route.component.to_lowercase())
                            });
                            if let Some(arr) = route_data["page_routes"].as_array_mut() {
                                arr.push(route_info);
                            }
                        }
                    }
                }
            }
            Err(e) => {
                println!("cargo:warning=Failed to load routes for metadata: {}", e);
                // Continue without route metadata
            }
        }
    }

    // Write JSON format
    let json_content = serde_json::to_string_pretty(&route_data)?;
    let json_path = info_path.join("data.json");
    std::fs::write(&json_path, &json_content)?;

    // Write TOML format - manually format since JSON to TOML conversion is problematic
    let mut toml_content = String::new();
    toml_content.push_str("# Route metadata generated at build time\n\n");
    if let Some(generated_at) = route_data["generated_at"].as_str() {
        toml_content.push_str(&format!("generated_at = \"{}\"\n", generated_at));
    }
    toml_content.push_str("components = []\n\n");
    toml_content.push_str("[[page_routes]]\n");

    // Write a simplified TOML format for compatibility
    if let Some(routes) = route_data["page_routes"].as_array() {
        for (i, route) in routes.iter().enumerate() {
            if i > 0 {
                toml_content.push_str("\n[[page_routes]]\n");
            }
            if let Some(path) = route.get("path").and_then(|p| p.as_str()) {
                toml_content.push_str(&format!("path = \"{}\"\n", path));
            }
            if let Some(component) = route.get("component").and_then(|c| c.as_str()) {
                toml_content.push_str(&format!("component = \"{}\"\n", component));
            }
            if let Some(language) = route.get("language").and_then(|l| l.as_str()) {
                toml_content.push_str(&format!("language = \"{}\"\n", language));
            }
            if let Some(enabled) = route.get("enabled").and_then(|e| e.as_bool()) {
                toml_content.push_str(&format!("enabled = {}\n", enabled));
            }
        }
    }
    let toml_path = info_path.join("data.toml");
    std::fs::write(&toml_path, &toml_content)?;

    // Save to cache for future builds
    cache.save_cache(&cache_key, "metadata_json", &json_content)?;
    cache.save_cache(&cache_key, "metadata_toml", &toml_content)?;
    println!("cargo:warning=✅ Route metadata generated and cached");

    Ok(())
}

/// Copy the generated resource registry to rustelo_core_lib's OUT_DIR
#[allow(dead_code)]
fn copy_resource_registry_to_core_lib() -> Result<(), Box<dyn std::error::Error>> {
    // Find the resource registry in our OUT_DIR
    let our_out_dir = env::var("OUT_DIR")?;
    let resource_registry_path = Path::new(&our_out_dir).join("resource_registry.rs");

    if !resource_registry_path.exists() {
        println!(
            "cargo:warning=Resource registry not found at {}",
            resource_registry_path.display()
        );
        return Ok(());
    }

    // Find the target directory
    let target_dir = Path::new(&our_out_dir)
        .ancestors()
        .find(|p| p.file_name().is_some_and(|name| name == "build"))
        .and_then(|build_dir| build_dir.parent())
        .ok_or("Could not find target directory")?;

    let mut copied_count = 0;

    // Copy to all possible target directories (server and client/WASM)
    let target_paths = vec![
        target_dir.join("debug/build"), // Server target
        target_dir.join("front/wasm32-unknown-unknown/debug/build"), // Client/WASM target
    ];

    let content = std::fs::read_to_string(&resource_registry_path)?;

    for build_dir in target_paths {
        if !build_dir.exists() {
            continue;
        }

        // Look for rustelo_core_lib build directories in this target
        if let Ok(entries) = std::fs::read_dir(&build_dir) {
            for entry in entries.flatten() {
                if entry
                    .file_name()
                    .to_string_lossy()
                    .starts_with("rustelo_core_lib-")
                {
                    let core_lib_out_dir = entry.path().join("out");
                    let dest_path = core_lib_out_dir.join("resource_registry.rs");

                    if let Ok(()) = std::fs::create_dir_all(&core_lib_out_dir) {
                        if let Ok(()) = std::fs::write(&dest_path, &content) {
                            println!(
                                "cargo:warning=✅ Copied resource registry to rustelo_core_lib: {}",
                                dest_path.display()
                            );
                            copied_count += 1;
                        }
                    }
                }
            }
        }
    }

    // NOTE: Removed rustc-cfg=has_generated_resources in Phase 2.11
    // The registration system no longer requires conditional compilation
    // Resources are registered dynamically at startup via resources::initialize()

    if copied_count > 0 {
        // NOTE: RUSTELO_HAS_GENERATED_RESOURCES env var kept for backward compatibility
        // but no longer used by the registration system
        println!("cargo:rustc-env=RUSTELO_HAS_GENERATED_RESOURCES=1");
        println!(
            "cargo:warning=📋 Successfully copied resource registry to {} locations",
            copied_count
        );
    } else {
        println!("cargo:warning=⚠️ No rustelo_core_lib build directories found to copy to");
        println!("cargo:warning=✅ But resource_registry.rs is generated for server use");
    }

    Ok(())
}

/// Create a clean copy of all generated files for code review
/// Removes cache core path and creates fresh copy in works-pv/out/
fn create_code_review_copy() -> Result<(), Box<dyn std::error::Error>> {
    let manifest = rustelo_utils::get_manifest()?;
    let root_dir = manifest.root();

    // Define out directory for code review (ephemeral, in works-pv/)
    let out_dir = root_dir.join("works-pv/out");

    // Remove existing out directory if it exists
    if out_dir.exists() {
        std::fs::remove_dir_all(&out_dir)?;
        println!("cargo:warning=🧹 Removed existing works-pv/out/ directory");
    }

    // Create fresh out directory structure
    std::fs::create_dir_all(&out_dir)?;
    let generated_dir = out_dir.join("generated");
    let cache_dir = out_dir.join("cache-snapshot");
    std::fs::create_dir_all(&generated_dir)?;
    std::fs::create_dir_all(&cache_dir)?;

    println!("cargo:warning=📁 Created fresh works-pv/out/ directory structure");

    // Copy generated files from BUILD_OUT_DIR
    let build_out_dir = env::var("OUT_DIR")?;
    copy_build_generated_files(&build_out_dir, &generated_dir)?;

    // Copy files from rustelo-cache if it exists
    copy_cache_files(root_dir, &cache_dir)?;

    // Copy any other generated files from target/
    copy_target_generated_files(root_dir, &out_dir)?;

    // Create summary file
    create_code_review_summary(&out_dir)?;

    println!("cargo:warning=✅ Code review copy created at: works-pv/out/");
    Ok(())
}

/// Copy generated files from build OUT_DIR
fn copy_build_generated_files(
    build_out_dir: &str,
    dest_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    let build_out_path = Path::new(build_out_dir);

    if !build_out_path.exists() {
        return Ok(());
    }

    // List of generated files to copy
    let generated_files = [
        "resource_registry.rs",
        "generated_routes.rs",
        "route_handlers.rs",
        "embedded_routes.toml",
        "config_constants.rs",
        "content_handlers.rs",
        "content_kinds_generated.rs",
        "server_config.rs",
    ];

    for file_name in &generated_files {
        let src_path = build_out_path.join(file_name);
        if src_path.exists() {
            let dest_path = dest_dir.join(file_name);
            std::fs::copy(&src_path, &dest_path)?;
            println!("cargo:warning=📄 Copied {}", file_name);
        }
    }

    // Copy generated_pages directory if it exists
    let generated_pages_src = build_out_path.join("generated_pages");
    if generated_pages_src.exists() {
        let generated_pages_dest = dest_dir.join("generated_pages");
        copy_directory(&generated_pages_src, &generated_pages_dest)?;
        println!("cargo:warning=📁 Copied generated_pages/ directory");
    }

    Ok(())
}

/// Copy files from rustelo-cache directories
fn copy_cache_files(root_dir: &Path, dest_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
    // Look for rustelo-cache in various locations
    let cache_locations = [
        root_dir.join("target/rustelo-cache"),
        root_dir.join("target/site_build"),
        root_dir.join("cache"),
    ];

    for cache_location in &cache_locations {
        if cache_location.exists() {
            let cache_name = cache_location
                .file_name()
                .unwrap_or_else(|| std::ffi::OsStr::new("cache"))
                .to_string_lossy();
            let dest_cache_dir = dest_dir.join(&*cache_name);
            copy_directory(cache_location, &dest_cache_dir)?;
            println!("cargo:warning=💾 Copied cache: {}", cache_name);
        }
    }

    Ok(())
}

/// Copy other relevant generated files from target/
fn copy_target_generated_files(
    root_dir: &Path,
    dest_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
    let target_dir = root_dir.join("target");
    if !target_dir.exists() {
        return Ok(());
    }

    let manifest_dir = dest_dir.join("build-manifests");
    std::fs::create_dir_all(&manifest_dir)?;

    // Copy any .toml files that might be build-generated
    if let Ok(entries) = std::fs::read_dir(&target_dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            if let Some(ext) = path.extension() {
                if ext == "toml"
                    && path
                        .file_name()
                        .map(|f| f.to_string_lossy().contains("embedded"))
                        .unwrap_or(false)
                {
                    if let Some(file_name) = path.file_name() {
                        let dest_path = manifest_dir.join(file_name);
                        if std::fs::copy(&path, &dest_path).is_ok() {
                            println!(
                                "cargo:warning=📋 Copied manifest: {}",
                                file_name.to_string_lossy()
                            );
                        }
                    }
                }
            }
        }
    }

    Ok(())
}

/// Recursively copy a directory
fn copy_directory(src: &Path, dest: &Path) -> Result<(), Box<dyn std::error::Error>> {
    if !src.exists() {
        return Ok(());
    }

    std::fs::create_dir_all(dest)?;

    for entry in std::fs::read_dir(src)? {
        let entry = entry?;
        let src_path = entry.path();
        let dest_path = dest.join(entry.file_name());

        if src_path.is_dir() {
            copy_directory(&src_path, &dest_path)?;
        } else {
            std::fs::copy(&src_path, &dest_path)?;
        }
    }

    Ok(())
}

/// Create a summary file for code review
fn create_code_review_summary(out_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
    let summary_content = format!(
        r#"# Build Generated Files - Code Review

Generated at: {}
Build system: Rustelo website implementation

## Directory Structure

- `generated/` - Files generated during build process
  - `resource_registry.rs` - Auto-generated resource registry
  - `generated_routes.rs` - Route handlers
  - `route_handlers.rs` - Route implementations
  - `embedded_routes.toml` - Route configuration
  - `config_constants.rs` - Configuration constants
  - `content_handlers.rs` - Content handling code
  - `content_kinds_generated.rs` - Content type definitions
  - `server_config.rs` - Server configuration
  - `generated_pages/` - Individual page components

- `cache-snapshot/` - Snapshot of build caches
  - `rustelo-cache/` - Rustelo smart caching system
  - `site_build/` - Site build cache

- `build-manifests/` - Build-time manifests and metadata

## Build Process

1. **Resource Discovery**: Scans site/ directory for configuration files
2. **Resource Generation**: Creates resource registry with all assets
3. **Route Generation**: Generates route handlers from configuration
4. **Caching**: Implements smart caching for faster rebuilds
5. **Code Review Copy**: This clean snapshot for review

## Key Features

- **Configuration-driven**: No hardcoded paths or routes
- **Language-agnostic**: Supports multiple languages dynamically
- **Smart caching**: Incremental builds with hash-based invalidation
- **PAP compliant**: Follows Pattern-Aligned Programming principles

## Files for Review

All generated files maintain the configuration-driven architecture.
No anti-PAP fallbacks or hardcoded paths should be present.

Generated on: {}
"#,
        chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC"),
        chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
    );

    let summary_path = out_dir.join("README.md");
    std::fs::write(summary_path, summary_content)?;

    // Also create a simple file list
    let mut file_list = String::new();
    file_list.push_str("# Generated Files List\n\n");

    collect_file_list(out_dir, &mut file_list, 0)?;

    let file_list_path = out_dir.join("file-list.txt");
    std::fs::write(file_list_path, file_list)?;

    Ok(())
}

/// Recursively collect file list for summary
fn collect_file_list(
    dir: &Path,
    output: &mut String,
    depth: usize,
) -> Result<(), Box<dyn std::error::Error>> {
    if !dir.exists() {
        return Ok(());
    }

    let indent = "  ".repeat(depth);

    for entry in std::fs::read_dir(dir)? {
        let entry = entry?;
        let path = entry.path();
        let name = entry.file_name().to_string_lossy().to_string();

        if path.is_dir() {
            output.push_str(&format!("{}📁 {}/\n", indent, name));
            collect_file_list(&path, output, depth + 1)?;
        } else {
            let size = std::fs::metadata(&path)?.len();
            output.push_str(&format!("{}📄 {} ({} bytes)\n", indent, name, size));
        }
    }

    Ok(())
}
