//! Rustelo CLI — project scaffolding and feature management tool
//!
//! Commands:
//! - `new`            Create a new project from templates
//! - `add/remove`     Add or remove Rustelo features
//! - `update`         Update framework path references
//! - `status`         Show project architecture status
//! - `override`       Create local configuration/component/page overrides
//! - `list-overrides` List active overrides
//! - `trace`          Show layered-override resolution path
//! - `remove-override` Delete a local override

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use console::style;
use dialoguer::{theme::ColorfulTheme, Confirm, MultiSelect};
use indicatif::{ProgressBar, ProgressStyle};
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

// ─── CLI surface ─────────────────────────────────────────────────────────────

#[derive(Parser)]
#[command(
    name = "rustelo",
    about = "Rustelo Framework CLI",
    version = "0.1.0"
)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Create a new Rustelo project from templates
    New {
        /// Project name (used for directory and crate names)
        name: String,
        /// Template type: basic, minimal, cms, enterprise, saas
        #[arg(short, long, default_value = "basic")]
        template: String,
        /// Comma-separated features: content-static,auth,email,analytics...
        #[arg(short, long, action = clap::ArgAction::Append)]
        features: Vec<String>,
        /// Output directory (defaults to `name`)
        #[arg(short, long)]
        path: Option<PathBuf>,
        /// Skip interactive prompts, use defaults
        #[arg(short, long)]
        quiet: bool,
    },
    /// Add a Rustelo feature to the current project
    Add {
        /// Feature name: analytics, smart-build, debugging-tools, auth, email, tls, metrics
        feature: String,
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
    },
    /// Remove a Rustelo feature from the current project
    Remove {
        /// Feature name
        feature: String,
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
        /// Skip confirmation prompt
        #[arg(short, long)]
        force: bool,
    },
    /// Update framework path references after moving projects
    Update {
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
        /// Re-sync rustelo.toml from templates
        #[arg(long)]
        sync_config: bool,
    },
    /// Check project architecture status
    Status {
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
    },
    /// Create a local override for config, component, or page
    Override {
        /// Override type: config | component | page
        override_type: String,
        /// Item name (e.g. `justfile`, `NavBar`, `HomePage`)
        name: String,
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
    },
    /// List all active local overrides
    ListOverrides {
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
    },
    /// Show layered-override resolution path for an item
    Trace {
        /// Trace type: config | component | page
        trace_type: String,
        /// Item name
        name: String,
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
    },
    /// Remove a local override (restores next-layer behaviour)
    RemoveOverride {
        /// Override type: config | component | page
        override_type: String,
        /// Item name
        name: String,
        /// Project directory (defaults to `.`)
        #[arg(short, long)]
        path: Option<PathBuf>,
        /// Skip confirmation prompt
        #[arg(short, long)]
        force: bool,
    },
}

// ─── Data structures ──────────────────────────────────────────────────────────


#[derive(Debug)]
struct ProjectStatus {
    has_workspace_cargo: bool,
    has_server_crate: bool,
    has_client_crate: bool,
    has_rustelo_config: bool,
    active_features: Vec<String>,
    framework_dir: Option<PathBuf>,
}

// ─── Entry point ─────────────────────────────────────────────────────────────

#[tokio::main]
async fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::New { name, template, features, path, quiet } => {
            create_new_project(&name, &template, features, path, quiet).await?;
        }
        Commands::Add { feature, path } => {
            add_feature(&feature, path).await?;
        }
        Commands::Remove { feature, path, force } => {
            remove_feature(&feature, path, force).await?;
        }
        Commands::Update { path, sync_config } => {
            update_project(path, sync_config).await?;
        }
        Commands::Status { path } => {
            show_status(path).await?;
        }
        Commands::Override { override_type, name, path } => {
            create_local_override(&override_type, &name, path).await?;
        }
        Commands::ListOverrides { path } => {
            list_active_overrides(path).await?;
        }
        Commands::Trace { trace_type, name, path } => {
            trace_resolution(&trace_type, &name, path).await?;
        }
        Commands::RemoveOverride { override_type, name, path, force } => {
            remove_local_override(&override_type, &name, path, force).await?;
        }
    }

    Ok(())
}

// ─── Framework discovery ──────────────────────────────────────────────────────

/// Locate the Rustelo framework root directory.
///
/// Resolution order:
/// 1. `RUSTELO_FRAMEWORK_DIR` environment variable
/// 2. Walk up from the current directory looking for `crates/templates/server-template`
fn find_framework_dir() -> Result<PathBuf> {
    if let Ok(dir) = std::env::var("RUSTELO_FRAMEWORK_DIR") {
        let p = PathBuf::from(dir);
        if p.join("crates/templates/server-template").exists() {
            return Ok(p.canonicalize()?);
        }
        anyhow::bail!(
            "RUSTELO_FRAMEWORK_DIR is set to '{}' but it doesn't contain crates/templates/server-template",
            p.display()
        );
    }

    // Walk up from cwd
    let mut dir = std::env::current_dir()?;
    loop {
        if dir.join("crates/templates/server-template").exists() {
            return Ok(dir);
        }
        if !dir.pop() {
            break;
        }
    }

    anyhow::bail!(
        "Rustelo framework directory not found.\n\
         Set the RUSTELO_FRAMEWORK_DIR environment variable to the rustelo repository root."
    )
}

// ─── Name utilities ───────────────────────────────────────────────────────────

fn to_kebab(name: &str) -> String {
    name.replace('_', "-")
}

fn to_snake(name: &str) -> String {
    name.replace('-', "_")
}

// ─── Template substitution ────────────────────────────────────────────────────

/// Apply project-name and framework-path substitutions to template file content.
///
/// Path dependencies in templates use relative paths from `crates/templates/{crate}/`.
/// After copying to a new location those relative paths break, so we replace them
/// with absolute paths derived from `framework_root`.
fn substitute_template(content: &str, name: &str, framework_root: &Path) -> String {
    let kebab = to_kebab(name);
    let snake = to_snake(name);
    let fw = framework_root.to_string_lossy();

    // Path substitutions come first to avoid partial replacements
    let content = content
        .replace(
            "path = \"../../../../deps/",
            &format!("path = \"{fw}/../deps/"),
        )
        .replace(
            "path = \"../../framework/",
            &format!("path = \"{fw}/framework/"),
        )
        .replace(
            "path = \"../../foundation/",
            &format!("path = \"{fw}/foundation/"),
        );

    // Name substitutions (order matters: longer patterns first)
    content
        .replace("my-site-server", &format!("{kebab}-server"))
        .replace("my-site-client", &format!("{kebab}-client"))
        .replace("my_site_server", &format!("{snake}_server"))
        .replace("my_site_client", &format!("{snake}_client"))
        .replace("site_server", &format!("{snake}_server"))
        .replace("site_client", &format!("{snake}_client"))
        .replace(
            "Minimal Rustelo server — replace with your site name",
            &format!("{name} server"),
        )
        .replace(
            "Minimal Rustelo WASM client — replace with your site name",
            &format!("{name} WASM client"),
        )
}

/// Recursively copy `src` to `dst`, applying `substitute_template` to all
/// text files (`.rs`, `.toml`, `.md`, `.env`, `.txt`, `build.rs`).
fn copy_and_substitute(src: &Path, dst: &Path, name: &str, framework_root: &Path) -> Result<()> {
    fs::create_dir_all(dst)?;

    for entry in WalkDir::new(src) {
        let entry = entry?;
        let path = entry.path();
        let relative = path.strip_prefix(src)?;
        let dst_path = dst.join(relative);

        if path.is_dir() {
            fs::create_dir_all(&dst_path)?;
        } else {
            if let Some(parent) = dst_path.parent() {
                fs::create_dir_all(parent)?;
            }
            let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
            let is_text = matches!(ext, "rs" | "toml" | "md" | "env" | "txt" | "ncl" | "ftl")
                || path.file_name().is_some_and(|n| n == "build.rs" || n == "justfile");

            if is_text {
                let content = fs::read_to_string(path)?;
                fs::write(&dst_path, substitute_template(&content, name, framework_root))?;
            } else {
                fs::copy(path, &dst_path)?;
            }
        }
    }

    Ok(())
}

// ─── Project scaffolding ──────────────────────────────────────────────────────

async fn create_new_project(
    name: &str,
    template: &str,
    features: Vec<String>,
    path: Option<PathBuf>,
    quiet: bool,
) -> Result<()> {
    println!("{}", style("🚀 Creating new Rustelo project").bold().green());

    let project_path = path.unwrap_or_else(|| PathBuf::from(name));

    if project_path.exists() {
        let proceed = quiet
            || Confirm::with_theme(&ColorfulTheme::default())
                .with_prompt("Directory already exists. Continue?")
                .default(false)
                .interact()?;
        if !proceed {
            return Ok(());
        }
    }

    let framework_dir = find_framework_dir().context(
        "Cannot locate Rustelo framework.\n\
         Set RUSTELO_FRAMEWORK_DIR to the rustelo repository root.",
    )?;

    let selected_features = if !features.is_empty() {
        features
    } else if quiet {
        vec!["content-static".to_string()]
    } else {
        select_features(template)?
    };

    let templates_dir = framework_dir.join("crates/templates");

    let pb = ProgressBar::new(5);
    pb.set_style(
        ProgressStyle::with_template("  [{bar:30.cyan/blue}] {msg}")
            .unwrap()
            .progress_chars("=>-"),
    );

    pb.set_message("Copying server template...");
    copy_and_substitute(
        &templates_dir.join("server-template"),
        &project_path.join("crates/server"),
        name,
        &framework_dir,
    )?;
    pb.inc(1);

    pb.set_message("Copying client template...");
    copy_and_substitute(
        &templates_dir.join("client-template"),
        &project_path.join("crates/client"),
        name,
        &framework_dir,
    )?;
    pb.inc(1);

    pb.set_message("Creating workspace...");
    write_workspace_cargo_toml(&project_path, name)?;
    pb.inc(1);

    pb.set_message("Creating environment config...");
    write_env_template(&project_path, name)?;
    pb.inc(1);

    pb.set_message("Writing rustelo.toml...");
    write_rustelo_config(&project_path, name, &selected_features)?;
    pb.inc(1);

    pb.finish_and_clear();

    println!("{}", style("✅ Project created successfully!").bold().green());
    println!();
    println!("Next steps:");
    println!("  cd {}", project_path.display());
    println!("  source .env");
    println!("  cargo leptos watch");

    Ok(())
}

fn write_workspace_cargo_toml(project_path: &Path, name: &str) -> Result<()> {
    let kebab = to_kebab(name);
    let content = format!(
        r#"[workspace]
members = [
    "crates/server",
    "crates/client",
]
resolver = "2"

[workspace.package]
version   = "0.1.0"
edition   = "2021"

# ── Leptos package metadata (cargo-leptos) ────────────────────────────────────
[[workspace.metadata.leptos]]
name             = "{kebab}"
bin-package      = "{kebab}-server"
lib-package      = "{kebab}-client"
output-name      = "{kebab}-client"
site-root        = "target/site"
site-pkg-dir     = "pkg"
site-addr        = "127.0.0.1:3000"
reload-port      = 3001
browserquery     = "defaults"
env              = "DEV"
"#
    );
    fs::write(project_path.join("Cargo.toml"), content)?;
    Ok(())
}

fn write_env_template(project_path: &Path, name: &str) -> Result<()> {
    let snake = to_snake(name);
    let content = format!(
        "# Generated by cargo rustelo new\n\
         NCL_CONFIG_PATH=site/config/index.ncl\n\
         LEPTOS_OUTPUT_NAME={snake}_client\n\
         SITE_CONTENT_PATH=site\n\
         DATABASE_URL=sqlite:target/{snake}.db\n"
    );
    fs::write(project_path.join(".env"), content)?;
    Ok(())
}

fn write_rustelo_config(project_path: &Path, name: &str, features: &[String]) -> Result<()> {
    let mut content = format!(
        "[project]\n\
         name = \"{name}\"\n\
         framework = \"rustelo\"\n\
         \n\
         [features]\n"
    );
    for feature in features {
        content.push_str(&format!("{feature} = true\n"));
    }
    fs::write(project_path.join("rustelo.toml"), content)?;
    Ok(())
}

fn select_features(template: &str) -> Result<Vec<String>> {
    let available: &[&str] = &[
        "content-static",
        "content-db",
        "auth",
        "email",
        "tls",
        "metrics",
        "analytics",
        "smart-build",
        "debugging-tools",
    ];

    let default_indices: &[usize] = match template {
        "minimal" => &[0],
        "basic" => &[0],
        "cms" => &[0, 1, 2],
        "enterprise" => &[0, 1, 2, 3, 4, 5],
        "saas" => &[0, 1, 2, 3, 4, 5, 6],
        _ => &[0],
    };

    let default_bools: Vec<bool> = (0..available.len())
        .map(|i| default_indices.contains(&i))
        .collect();

    let selections = MultiSelect::with_theme(&ColorfulTheme::default())
        .with_prompt("Select features to enable")
        .items(available)
        .defaults(&default_bools)
        .interact()?;

    Ok(selections.into_iter().map(|i| available[i].to_string()).collect())
}

// ─── Feature management ───────────────────────────────────────────────────────

/// Known Rustelo features and their descriptions.
fn known_features() -> &'static [(&'static str, &'static str)] {
    &[
        ("content-static", "Static file content with Markdown support"),
        ("content-db", "Database-backed content management"),
        ("auth", "JWT/OAuth2 authentication with RBAC"),
        ("email", "Multi-provider email system"),
        ("tls", "HTTPS/TLS support via rustls"),
        ("metrics", "Prometheus metrics endpoint"),
        ("analytics", "Navigation and server analytics"),
        ("smart-build", "Incremental builds with intelligent caching"),
        ("debugging-tools", "Enhanced server/browser debugging"),
    ]
}

async fn add_feature(feature: &str, path: Option<PathBuf>) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    if !known_features().iter().any(|(name, _)| *name == feature) {
        eprintln!(
            "⚠️  Unknown feature '{feature}'. Known features: {}",
            known_features()
                .iter()
                .map(|(n, _)| *n)
                .collect::<Vec<_>>()
                .join(", ")
        );
        return Ok(());
    }

    println!(
        "{} {}",
        style("🎛️ Adding feature:").bold().green(),
        style(feature).yellow()
    );

    update_rustelo_config_feature(&project_path, feature, true)?;

    // Apply feature template files if they exist in the framework
    if let Ok(framework_dir) = find_framework_dir() {
        let feature_template = framework_dir.join("features").join(feature);
        if feature_template.exists() {
            println!("  📋 Applying feature template files...");
            apply_feature_template(&feature_template, &project_path, feature)?;
        }
    }

    println!("✅ Feature '{feature}' added to rustelo.toml");
    println!("💡 Run 'cargo leptos watch' to rebuild with the new feature");
    Ok(())
}

async fn remove_feature(feature: &str, path: Option<PathBuf>, force: bool) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    println!(
        "{} {}",
        style("🗑️ Removing feature:").bold().yellow(),
        style(feature).red()
    );

    if !force
        && !Confirm::with_theme(&ColorfulTheme::default())
            .with_prompt(format!("Remove feature '{feature}'?"))
            .default(false)
            .interact()?
    {
        return Ok(());
    }

    update_rustelo_config_feature(&project_path, feature, false)?;

    println!("✅ Feature '{feature}' disabled in rustelo.toml");
    println!("💡 Feature files remain — remove manually if desired");
    Ok(())
}

fn update_rustelo_config_feature(project_path: &Path, feature: &str, add: bool) -> Result<()> {
    let config_path = project_path.join("rustelo.toml");
    let mut content = if config_path.exists() {
        fs::read_to_string(&config_path)?
    } else {
        "[project]\nname = \"project\"\nframework = \"rustelo\"\n\n[features]\n".to_string()
    };

    let line = format!("{feature} = true\n");
    let comment = format!("# {feature} = false\n");

    if add {
        if !content.contains(&line) && !content.contains(&comment) {
            if !content.contains("[features]") {
                content.push_str("\n[features]\n");
            }
            content.push_str(&line);
        } else {
            content = content.replace(&comment, &line);
        }
    } else {
        content = content.replace(&line, &comment);
    }

    fs::write(config_path, content)?;
    Ok(())
}

fn apply_feature_template(
    feature_path: &Path,
    project_path: &Path,
    feature_name: &str,
) -> Result<()> {
    for entry in WalkDir::new(feature_path) {
        let entry = entry?;
        let src = entry.path();
        let relative = src.strip_prefix(feature_path)?;
        let dst = project_path.join(relative);

        if src.is_dir() {
            fs::create_dir_all(&dst)?;
        } else {
            if let Some(parent) = dst.parent() {
                fs::create_dir_all(parent)?;
            }
            if let Some(name) = dst.file_name() {
                if name == "Cargo.toml" && dst.exists() {
                    merge_cargo_toml(&dst, src, feature_name)?;
                } else if name == "justfile" && dst.exists() {
                    merge_justfile(&dst, src, feature_name)?;
                } else {
                    fs::copy(src, &dst)?;
                }
            }
        }
    }
    Ok(())
}

// ─── Update ───────────────────────────────────────────────────────────────────

async fn update_project(path: Option<PathBuf>, sync_config: bool) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    println!("{}", style("🔄 Updating project").bold().blue());

    create_backup(&project_path).await?;

    if sync_config {
        // Re-generate rustelo.toml from current state
        println!("  📋 Config sync not yet automated — edit rustelo.toml manually.");
    }

    println!("✅ Update complete (backup created in .backup/)");
    Ok(())
}

// ─── Status ───────────────────────────────────────────────────────────────────

async fn show_status(path: Option<PathBuf>) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));
    let status = collect_project_status(&project_path)?;
    print_project_status(&status);
    Ok(())
}

fn collect_project_status(project_path: &Path) -> Result<ProjectStatus> {
    let rustelo_config = project_path.join("rustelo.toml");
    let active_features = if rustelo_config.exists() {
        let content = fs::read_to_string(&rustelo_config)?;
        content
            .lines()
            .filter(|l| l.contains(" = true"))
            .filter_map(|l| l.split('=').next())
            .map(|s| s.trim().to_string())
            .filter(|s| !s.is_empty())
            .collect()
    } else {
        vec![]
    };

    Ok(ProjectStatus {
        has_workspace_cargo: project_path.join("Cargo.toml").exists(),
        has_server_crate: project_path.join("crates/server").exists(),
        has_client_crate: project_path.join("crates/client").exists(),
        has_rustelo_config: rustelo_config.exists(),
        active_features,
        framework_dir: find_framework_dir().ok(),
    })
}

fn print_project_status(status: &ProjectStatus) {
    println!("{}", style("📊 Project Status").bold().cyan());
    println!();

    let check = |ok: bool| if ok { "✅" } else { "❌" };
    println!("{} Workspace Cargo.toml", check(status.has_workspace_cargo));
    println!("{} crates/server", check(status.has_server_crate));
    println!("{} crates/client", check(status.has_client_crate));
    println!("{} rustelo.toml", check(status.has_rustelo_config));

    match &status.framework_dir {
        Some(dir) => println!("✅ Framework: {}", dir.display()),
        None => println!("❌ Framework: not found (set RUSTELO_FRAMEWORK_DIR)"),
    }

    if !status.active_features.is_empty() {
        println!();
        println!("{}", style("Active features:").bold().yellow());
        for f in &status.active_features {
            println!("  • {f}");
        }
    }
}

// ─── Backup ───────────────────────────────────────────────────────────────────

async fn create_backup(project_path: &Path) -> Result<()> {
    let backup_dir = project_path.join(".backup");
    fs::create_dir_all(&backup_dir)?;

    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();

    let snapshot = backup_dir.join(format!("snapshot-{timestamp}"));
    fs::create_dir_all(&snapshot)?;

    // Only back up source files, not target/
    for entry in WalkDir::new(project_path).max_depth(4) {
        let entry = entry?;
        let path = entry.path();
        if let Ok(rel) = path.strip_prefix(project_path) {
            let rel_str = rel.to_string_lossy();
            if rel_str.starts_with("target") || rel_str.starts_with(".backup") {
                continue;
            }
            let dst = snapshot.join(rel);
            if path.is_dir() {
                fs::create_dir_all(&dst)?;
            } else if path.is_file() {
                if let Some(parent) = dst.parent() {
                    fs::create_dir_all(parent)?;
                }
                fs::copy(path, &dst)?;
            }
        }
    }

    println!("  📦 Backup created: {}", snapshot.display());
    Ok(())
}

// ─── Merge utilities ─────────────────────────────────────────────────────────

fn merge_cargo_toml(project_toml: &Path, feature_toml: &Path, feature_name: &str) -> Result<()> {
    let mut content = if project_toml.exists() {
        fs::read_to_string(project_toml)?
    } else {
        String::new()
    };
    let extra = fs::read_to_string(feature_toml)?;
    content.push_str(&format!("\n# {feature_name} feature\n"));
    content.push_str(&extra);
    fs::write(project_toml, content)?;
    Ok(())
}

fn merge_justfile(project_justfile: &Path, feature_justfile: &Path, feature_name: &str) -> Result<()> {
    let mut content = if project_justfile.exists() {
        fs::read_to_string(project_justfile)?
    } else {
        String::new()
    };
    let extra = fs::read_to_string(feature_justfile)?;
    content.push_str(&format!("\n# {feature_name} commands\n"));
    content.push_str(&extra);
    fs::write(project_justfile, content)?;
    Ok(())
}

// ─── Override system ──────────────────────────────────────────────────────────

async fn create_local_override(override_type: &str, name: &str, path: Option<PathBuf>) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    println!(
        "{} {} '{}'",
        style("🎛️ Creating local override:").bold().green(),
        override_type,
        style(name).yellow()
    );

    match override_type {
        "config" => create_config_override(&project_path, name).await?,
        "component" => create_component_override(&project_path, name).await?,
        "page" => create_page_override(&project_path, name).await?,
        _ => {
            eprintln!("⚠️  Unknown override type '{override_type}'. Use: config | component | page");
            return Ok(());
        }
    }

    println!("✅ Override created — edit the file to customise behaviour");
    Ok(())
}

async fn create_config_override(project_path: &Path, config_name: &str) -> Result<()> {
    let dir = project_path.join("config/local");
    fs::create_dir_all(&dir)?;
    let dest = dir.join(config_name);

    if dest.exists() {
        println!("⚠️  Already exists: {}", dest.display());
        return Ok(());
    }

    let _dynamic;
    let content: &str = match config_name {
        "rustelo.toml" => {
            r#"# Local Rustelo configuration (highest precedence)
[project]
# name = "my-project"

[features]
# analytics = true
# auth = true

[dev]
# debug_mode = true
"#
        }
        "justfile" => {
            r#"# Local justfile overrides (highest precedence)
# Add custom recipes here

# example:
# my-task:
#   echo "Custom task"
"#
        }
        _ => {
            _dynamic = format!(
                "# Local override for {config_name}\n\
                 # Highest precedence in the layered override system\n\n"
            );
            &_dynamic
        }
    };

    fs::write(&dest, content)?;
    println!("  📝 Created: {}", dest.display());
    Ok(())
}

async fn create_component_override(project_path: &Path, component_name: &str) -> Result<()> {
    let dir = project_path.join("src/components/local");
    fs::create_dir_all(&dir)?;

    let filename = format!("{component_name}.rs");
    let dest = dir.join(&filename);

    if dest.exists() {
        println!("⚠️  Already exists: {}", dest.display());
        return Ok(());
    }

    let component_lower = component_name.to_lowercase();
    let content = format!(
        r#"//! Local override for {component_name} component
//! Highest precedence in the layered override system

use leptos::prelude::*;

#[component]
pub fn {component_name}() -> impl IntoView {{
    view! {{
        <div class="{component_lower}">
        </div>
    }}
}}
"#
    );

    fs::write(&dest, content)?;
    println!("  📝 Created: {}", dest.display());
    Ok(())
}

async fn create_page_override(project_path: &Path, page_name: &str) -> Result<()> {
    let dir = project_path.join("src/pages/local");
    fs::create_dir_all(&dir)?;

    let filename = format!("{page_name}.rs");
    let dest = dir.join(&filename);

    if dest.exists() {
        println!("⚠️  Already exists: {}", dest.display());
        return Ok(());
    }

    let page_name_lower = page_name.to_lowercase();
    let content = format!(
        r#"//! Local override for {page_name} page
//! Highest precedence in the layered override system

use leptos::prelude::*;

#[component]
pub fn {page_name}Page() -> impl IntoView {{
    view! {{
        <div class="page-{page_name_lower}">
            <h1>"Local {page_name} Page"</h1>
        </div>
    }}
}}
"#
    );

    fs::write(&dest, content)?;
    println!("  📝 Created: {}", dest.display());
    Ok(())
}

async fn list_active_overrides(path: Option<PathBuf>) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    println!("{}", style("📋 Active Local Overrides").bold().cyan());
    println!();

    let mut found = false;

    let sections: &[(&str, &str)] = &[
        ("Configuration", "config/local"),
        ("Components",    "src/components/local"),
        ("Pages",         "src/pages/local"),
    ];

    for (label, rel_dir) in sections {
        let dir = project_path.join(rel_dir);
        if !dir.exists() {
            continue;
        }
        let mut entries: Vec<_> = fs::read_dir(&dir)?
            .filter_map(|e| e.ok())
            .filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
            .collect();
        entries.sort_by_key(|e| e.file_name());
        if entries.is_empty() {
            continue;
        }

        println!("{}", style(format!("{label}:")).bold().yellow());
        for entry in entries {
            let name = entry.file_name().to_string_lossy().to_string();
            if name == "mod.rs" {
                continue;
            }
            let display = name.strip_suffix(".rs").unwrap_or(&name);
            println!("  ✅ {display}");
            found = true;
        }
        println!();
    }

    if !found {
        println!("No local overrides found.");
        println!("💡 Use 'cargo rustelo override <type> <name>' to create one");
    }

    Ok(())
}

async fn trace_resolution(trace_type: &str, name: &str, path: Option<PathBuf>) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    println!(
        "{} {} '{}'",
        style("🔍 Resolution trace:").bold().blue(),
        trace_type,
        style(name).yellow()
    );
    println!();

    let (layers, file_suffix): (Vec<(&str, PathBuf)>, &str) = match trace_type {
        "config" => (
            vec![
                ("Local",    project_path.join("config/local").join(name)),
                ("Features", project_path.join("config/features")),
                ("Template", project_path.join("config/templates").join(name)),
            ],
            "",
        ),
        "component" => (
            vec![
                ("Local",    project_path.join("src/components/local").join(format!("{name}.rs"))),
                ("Features", project_path.join("src/components/features")),
                ("Template", project_path.join("src/components/templates").join(format!("{name}.rs"))),
            ],
            ".rs",
        ),
        "page" => (
            vec![
                ("Local",    project_path.join("src/pages/local").join(format!("{name}.rs"))),
                ("Features", project_path.join("src/pages/features")),
                ("Template", project_path.join("src/pages/templates").join(format!("{name}.rs"))),
            ],
            ".rs",
        ),
        _ => {
            eprintln!("⚠️  Unknown trace type '{trace_type}'. Use: config | component | page");
            return Ok(());
        }
    };

    let mut winner_found = false;

    for (layer_name, layer_path) in &layers {
        let exists = if *layer_name == "Features" {
            layer_path.exists()
                && fs::read_dir(layer_path)
                    .ok()
                    .map(|entries| {
                        entries
                            .filter_map(|e| e.ok())
                            .any(|e| e.path().join(format!("{name}{file_suffix}")).exists())
                    })
                    .unwrap_or(false)
        } else {
            layer_path.exists()
        };

        let (icon, status_label) = if exists && !winner_found {
            winner_found = true;
            ("✅", style("ACTIVE").green().bold().to_string())
        } else if exists {
            ("🔸", style("OVERRIDDEN").yellow().to_string())
        } else {
            ("❌", style("not found").red().to_string())
        };

        println!("{icon} {layer_name:<12} {path}  {status_label}",
            path = layer_path.display());
    }

    println!("   Framework    (always available — provided by rustelo crates)  {}", style("fallback").dim());

    Ok(())
}

async fn remove_local_override(
    override_type: &str,
    name: &str,
    path: Option<PathBuf>,
    force: bool,
) -> Result<()> {
    let project_path = path.unwrap_or_else(|| PathBuf::from("."));

    println!(
        "{} {} '{}'",
        style("🗑️ Removing local override:").bold().yellow(),
        override_type,
        style(name).red()
    );

    if !force
        && !Confirm::with_theme(&ColorfulTheme::default())
            .with_prompt(format!("Remove local override for {override_type} '{name}'?"))
            .default(false)
            .interact()?
    {
        return Ok(());
    }

    let override_path = match override_type {
        "config" => project_path.join("config/local").join(name),
        "component" => project_path
            .join("src/components/local")
            .join(format!("{name}.rs")),
        "page" => project_path
            .join("src/pages/local")
            .join(format!("{name}.rs")),
        _ => {
            eprintln!("⚠️  Unknown override type '{override_type}'");
            return Ok(());
        }
    };

    if override_path.exists() {
        fs::remove_file(&override_path)?;
        println!("✅ Removed: {}", override_path.display());
        println!("💡 The next layer in the resolution chain now takes effect");
    } else {
        println!("⚠️  No override found for {override_type} '{name}'");
    }

    Ok(())
}

// ─── Template utilities ───────────────────────────────────────────────────────

/// Recursively copy `src` to `dst` without substitution (used internally).
#[allow(dead_code)]
fn copy_directory(src: &Path, dst: &Path) -> Result<()> {
    fs::create_dir_all(dst)?;
    for entry in WalkDir::new(src) {
        let entry = entry?;
        let path = entry.path();
        let rel = path.strip_prefix(src)?;
        let dst_path = dst.join(rel);
        if path.is_dir() {
            fs::create_dir_all(&dst_path)?;
        } else {
            if let Some(parent) = dst_path.parent() {
                fs::create_dir_all(parent)?;
            }
            fs::copy(path, &dst_path)?;
        }
    }
    Ok(())
}
