envx_core/
wizard.rs

1#![allow(clippy::format_push_string)]
2use color_eyre::Result;
3use color_eyre::eyre::eyre;
4use dialoguer::{Confirm, Input, MultiSelect, Select, theme::ColorfulTheme};
5use std::{
6    fs,
7    path::{Path, PathBuf},
8};
9
10use ahash::AHashMap as HashMap;
11use colored::Colorize;
12use glob::glob;
13use serde::{Deserialize, Serialize};
14
15use crate::{ProfileManager, ProjectConfig, RequiredVar, ValidationRules as ConfigValidationRules};
16
17// Custom error type for ESC handling
18#[derive(Debug)]
19struct EscPressed;
20
21impl std::fmt::Display for EscPressed {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "User pressed ESC")
24    }
25}
26
27impl std::error::Error for EscPressed {}
28
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30pub struct WizardConfig {
31    pub skip_system_check: bool,
32    pub auto_detect_project: bool,
33    pub default_profiles: Vec<String>,
34    pub template_path: Option<PathBuf>,
35    pub selected_vars: Vec<SelectedVariable>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SelectedVariable {
40    pub name: String,
41    pub value: String,
42    pub description: String,
43    pub required: bool,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct ProjectType {
48    pub name: String,
49    pub category: ProjectCategory,
50    pub suggested_vars: Vec<SuggestedVariable>,
51    pub suggested_profiles: Vec<String>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub enum ProjectCategory {
56    WebApp,
57    Python,
58    Rust,
59    Go,
60    NextJs,
61    Docker,
62    Microservices,
63    Custom,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct SuggestedVariable {
68    pub name: String,
69    pub description: String,
70    pub example: String,
71    pub required: bool,
72    pub sensitive: bool,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct TeamConfig {
77    pub config_path: PathBuf,
78    pub git_hooks: bool,
79    pub ci_integration: bool,
80    pub shared_profiles: bool,
81}
82
83#[allow(clippy::struct_excessive_bools)]
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ValidationRules {
86    pub require_all_defined: bool,
87    pub validate_urls: bool,
88    pub validate_numbers: bool,
89    pub warn_missing: bool,
90    pub strict_mode: bool,
91    pub custom_patterns: HashMap<String, String>,
92}
93
94#[allow(clippy::struct_excessive_bools)]
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct Integrations {
97    pub shell_aliases: bool,
98    pub auto_completion: bool,
99    pub vscode_extension: bool,
100    pub git_hooks: bool,
101    pub docker_integration: bool,
102}
103
104#[derive(Debug, Clone)]
105pub struct SystemInfo {
106    pub os: String,
107    pub shell: String,
108    pub terminal: String,
109    pub home_dir: PathBuf,
110    pub config_dir: PathBuf,
111}
112
113impl SystemInfo {
114    /// Detects system information including OS, shell, terminal, and directories.
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if:
119    /// - The home directory cannot be determined
120    /// - The config directory cannot be determined
121    pub fn detect() -> Result<Self> {
122        let os = if cfg!(windows) {
123            "Windows".to_string()
124        } else if cfg!(target_os = "macos") {
125            "macOS".to_string()
126        } else {
127            "Linux".to_string()
128        };
129
130        let shell = std::env::var("SHELL").unwrap_or_else(|_| {
131            if cfg!(windows) {
132                "PowerShell".to_string()
133            } else {
134                "bash".to_string()
135            }
136        });
137
138        let terminal = std::env::var("TERM_PROGRAM")
139            .or_else(|_| std::env::var("TERMINAL"))
140            .unwrap_or_else(|_| "Unknown".to_string());
141
142        let home_dir = dirs::home_dir().ok_or_else(|| eyre!("Could not find home directory"))?;
143        let config_dir = dirs::config_dir().ok_or_else(|| eyre!("Could not find config directory"))?;
144
145        Ok(Self {
146            os,
147            shell,
148            terminal,
149            home_dir,
150            config_dir,
151        })
152    }
153}
154
155#[derive(Default)]
156pub struct SetupWizard {
157    theme: ColorfulTheme,
158    config: WizardConfig,
159}
160
161#[derive(Debug, Clone)]
162pub struct SetupResult {
163    pub project_type: ProjectType,
164    pub profiles: Vec<String>,
165    pub profile_configs: HashMap<String, HashMap<String, String>>,
166    pub team_config: Option<TeamConfig>,
167    pub validation_rules: ValidationRules,
168    pub imported_files: Vec<PathBuf>,
169    pub create_env_files: bool,
170    pub selected_vars: Vec<SelectedVariable>,
171}
172
173impl SetupWizard {
174    #[must_use]
175    pub fn new() -> Self {
176        Self::default()
177    }
178
179    /// Runs the setup wizard and returns the configuration result.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if:
184    /// - System detection fails
185    /// - User input validation fails
186    /// - File I/O operations fail during configuration
187    /// - Profile creation fails
188    /// - Configuration file creation fails
189    pub fn run(&mut self) -> Result<SetupResult> {
190        // Wrap the entire wizard in error handling for ESC
191        match self.run_wizard() {
192            Ok(result) => Ok(result),
193            Err(e) => {
194                if e.downcast_ref::<EscPressed>().is_some() {
195                    Self::show_goodbye();
196                    std::process::exit(0);
197                } else {
198                    Err(e)
199                }
200            }
201        }
202    }
203
204    fn run_wizard(&mut self) -> Result<SetupResult> {
205        // Step 1: Welcome
206        Self::show_welcome()?;
207
208        // Step 2: Detect system
209        let system_info = Self::detect_system()?;
210        Self::show_system_info(&system_info);
211
212        // Step 3: Project type
213        let project_type = self.select_project_type()?;
214
215        // Step 4: Import existing files
216        let imported_files = if let Some(existing_files) = self.scan_existing_files()? {
217            self.import_existing(existing_files)?
218        } else {
219            Vec::new()
220        };
221
222        // Step 5: Configure environment variables with values
223        let selected_vars = self.configure_variables(&project_type)?;
224
225        // Step 6: Create profiles with actual configurations
226        let (profiles, profile_configs) = self.create_and_configure_profiles(&project_type, &selected_vars)?;
227
228        // Step 7: Ask if user wants to create .env files
229        let create_env_files = self.ask_create_env_files()?;
230
231        // Step 8: Team setup
232        let team_config = if self.ask_team_setup()? {
233            Some(self.configure_team_features()?)
234        } else {
235            None
236        };
237
238        // Step 9: Validation rules
239        let validation_rules = self.configure_validation(&project_type)?;
240
241        // Step 10: Review and apply
242        let result = SetupResult {
243            project_type: project_type.clone(),
244            profiles,
245            profile_configs,
246            team_config,
247            validation_rules,
248            imported_files,
249            create_env_files,
250            selected_vars,
251        };
252
253        self.review_and_apply(&result)?;
254
255        // Step 11: Check if all required variables are set
256        Self::check_required_variables(&result);
257
258        Ok(result)
259    }
260
261    fn show_goodbye() {
262        println!("\n{}", "━".repeat(65).bright_black());
263        println!(
264            "\n{} {}",
265            "šŸ‘‹".bright_yellow(),
266            "Setup cancelled. No worries!".bright_cyan().bold()
267        );
268        println!(
269            "\n{}",
270            "You can run 'envx init' anytime to start the setup wizard again.".bright_white()
271        );
272        println!("{}", "Your project files remain unchanged.".bright_white());
273        println!("\n{}", "━".repeat(65).bright_black());
274        println!("\n{}", "Happy coding! šŸš€".bright_magenta());
275    }
276
277    #[allow(clippy::too_many_lines)]
278    fn show_welcome() -> Result<()> {
279        // Clear screen for a fresh start
280        print!("\x1B[2J\x1B[1;1H");
281
282        // Colorful ASCII art logo
283        println!(
284            "{}",
285            "╭─────────────────────────────────────────────────────────────╮".bright_cyan()
286        );
287        println!(
288            "{}",
289            "│                                                             │".bright_cyan()
290        );
291        println!(
292            "{}",
293            "│  ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā–ˆā•—   ā–ˆā–ˆā•—ā–ˆā–ˆā•—   ā–ˆā–ˆā•—ā–ˆā–ˆā•—  ā–ˆā–ˆā•—                        │".bright_cyan()
294        );
295        println!(
296            "{}",
297            "│  ā–ˆā–ˆā•”ā•ā•ā•ā•ā•ā–ˆā–ˆā–ˆā–ˆā•—  ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘   ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•—ā–ˆā–ˆā•”ā•                        │".bright_cyan()
298        );
299        println!(
300            "{}",
301            "│  ā–ˆā–ˆā–ˆā–ˆā–ˆā•—  ā–ˆā–ˆā•”ā–ˆā–ˆā•— ā–ˆā–ˆā•‘ā–ˆā–ˆā•‘   ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā•”ā•                         │".bright_cyan()
302        );
303        println!(
304            "{}",
305            "│  ā–ˆā–ˆā•”ā•ā•ā•  ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•—ā–ˆā–ˆā•‘ā•šā–ˆā–ˆā•— ā–ˆā–ˆā•”ā• ā–ˆā–ˆā•”ā–ˆā–ˆā•—                         │".bright_cyan()
306        );
307        println!(
308            "{}",
309            "│  ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā•—ā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā•‘ ā•šā–ˆā–ˆā–ˆā–ˆā•”ā•  ā–ˆā–ˆā•”ā• ā–ˆā–ˆā•—                       │".bright_cyan()
310        );
311        println!(
312            "{}",
313            "│  ā•šā•ā•ā•ā•ā•ā•ā•ā•šā•ā•  ā•šā•ā•ā•ā•  ā•šā•ā•ā•ā•   ā•šā•ā•  ā•šā•ā•                       │".bright_cyan()
314        );
315        println!(
316            "{}",
317            "│                                                             │".bright_cyan()
318        );
319        println!(
320            "{}",
321            format!(
322                "│           Environment Variable Manager v{:<8}            │",
323                env!("CARGO_PKG_VERSION")
324            )
325            .bright_cyan()
326        );
327        println!(
328            "{}",
329            "╰─────────────────────────────────────────────────────────────╯".bright_cyan()
330        );
331
332        println!(
333            "\n{} {} {}",
334            "✨".bright_yellow(),
335            "Welcome to envx!".bright_white().bold(),
336            "Your intelligent environment variable companion".bright_blue()
337        );
338
339        println!("\n{}", "━".repeat(65).bright_black());
340
341        // Feature highlights with icons
342        println!("\n{}", "This setup wizard will help you:".bright_white());
343
344        let features = vec![
345            (
346                "šŸ“‹",
347                "Define environment variables",
348                "Set up your project's environment",
349            ),
350            (
351                "šŸš€",
352                "Create profiles",
353                "Configure dev, test, and production environments",
354            ),
355            ("šŸ“¦", "Import existing files", "Seamlessly migrate from .env files"),
356            ("šŸ“", "Generate .env files", "Create .env files for each profile"),
357            ("šŸ‘„", "Enable team features", "Share configurations with your team"),
358        ];
359
360        for (icon, title, desc) in features {
361            println!(
362                "  {} {} {}",
363                icon,
364                format!("{title:<22}").bright_green().bold(),
365                format!("─ {desc}").bright_black()
366            );
367        }
368
369        println!("\n{}", "━".repeat(65).bright_black());
370
371        // Updated estimated time
372        println!(
373            "\n{} {} {}",
374            "ā±ļø ".bright_blue(),
375            "Estimated time:".bright_white(),
376            "1-3 minutes".bright_yellow().bold()
377        );
378
379        // Note about ESC
380        println!(
381            "\n{} {}",
382            "šŸ’”".bright_yellow(),
383            "Tip: Press ESC at any time to exit the wizard".bright_white().italic()
384        );
385
386        // Interactive prompt with better styling
387        println!(
388            "\n{}",
389            "Let's create the perfect setup for your project! šŸŽÆ".bright_magenta()
390        );
391
392        let continue_prompt = format!(
393            "\n{} {} {} {} {}",
394            "Type".bright_black(),
395            "[y (yes)]".bright_green().bold(),
396            "to begin your journey,".bright_black(),
397            "[n (no)]".bright_red().bold(),
398            "to skip, or [ESC] to exit".bright_black()
399        );
400
401        // Add a subtle animation effect
402        print!("\n{}", "Initializing".bright_cyan());
403        for _ in 0..3 {
404            std::thread::sleep(std::time::Duration::from_millis(400));
405            print!("{}", ".".bright_cyan());
406            std::io::Write::flush(&mut std::io::stdout())?;
407        }
408        println!(" {}", "Ready!".bright_green().bold());
409
410        println!("{continue_prompt}");
411
412        // Custom theme for this specific prompt
413        let welcome_theme = ColorfulTheme {
414            prompt_style: dialoguer::console::Style::new().cyan().bold(),
415            ..ColorfulTheme::default()
416        };
417
418        let result = Confirm::with_theme(&welcome_theme)
419            .with_prompt("")
420            .default(true)
421            .show_default(false)
422            .wait_for_newline(true)
423            .interact_opt()?;
424
425        match result {
426            Some(true) => {
427                // Clear and show a motivational message
428                println!(
429                    "\n{} {}",
430                    "šŸŽ‰".bright_yellow(),
431                    "Great choice! Let's build something amazing together."
432                        .bright_green()
433                        .bold()
434                );
435                std::thread::sleep(std::time::Duration::from_millis(1000));
436                Ok(())
437            }
438            Some(false) => {
439                println!(
440                    "\n{} {}",
441                    "šŸ‘‹".bright_yellow(),
442                    "No problem! You can run 'envx init' anytime to set up.".bright_blue()
443                );
444                std::process::exit(0);
445            }
446            None => Err(EscPressed.into()),
447        }
448    }
449
450    fn detect_system() -> Result<SystemInfo> {
451        println!("\nšŸ” Detecting your system...");
452
453        let info = SystemInfo::detect()?;
454        Ok(info)
455    }
456
457    fn show_system_info(info: &SystemInfo) {
458        println!("āœ“ OS: {}", info.os);
459        println!("āœ“ Shell: {}", info.shell);
460        println!("āœ“ Terminal: {}", info.terminal);
461        println!("āœ“ Envx Version: {}\n", env!("CARGO_PKG_VERSION"));
462    }
463
464    fn ask_team_setup(&self) -> Result<bool> {
465        match Confirm::with_theme(&self.theme)
466            .with_prompt("Are you working in a team?")
467            .default(false)
468            .interact_opt()?
469        {
470            Some(value) => Ok(value),
471            None => Err(EscPressed.into()),
472        }
473    }
474
475    fn ask_create_env_files(&self) -> Result<bool> {
476        match Confirm::with_theme(&self.theme)
477            .with_prompt("\nWould you like to create .env files for your profiles?")
478            .default(true)
479            .interact_opt()?
480        {
481            Some(value) => Ok(value),
482            None => Err(EscPressed.into()),
483        }
484    }
485
486    /// Prompts the user to select a project type from predefined options.
487    ///
488    /// # Errors
489    ///
490    /// Returns an error if:
491    /// - User interaction fails (e.g., terminal issues)
492    /// - User cancels the selection (ESC key)
493    /// - Custom project type creation fails
494    pub fn select_project_type(&self) -> Result<ProjectType> {
495        let options = vec![
496            "Web Application (Node.js, React, etc.)",
497            "Python Application",
498            "Rust Application",
499            "Go Application",
500            "Next.js/Full-Stack Application",
501            "Docker/Container-based",
502            "Multi-service/Microservices",
503            "Other/Custom",
504        ];
505
506        let Some(selection) = Select::with_theme(&self.theme)
507            .with_prompt("What type of project are you working on?")
508            .items(&options)
509            .interact_opt()?
510        else {
511            return Err(EscPressed.into());
512        };
513
514        let project_type = match selection {
515            0 => Self::create_web_app_type(),
516            1 => Self::create_python_type(),
517            2 => Self::create_rust_type(),
518            3 => Self::create_go_type(),
519            4 => Self::create_nextjs_type(),
520            5 => Self::create_docker_type(),
521            6 => Self::create_microservices_type(),
522            _ => self.create_custom_type()?,
523        };
524
525        Ok(project_type)
526    }
527
528    fn configure_variables(&mut self, project_type: &ProjectType) -> Result<Vec<SelectedVariable>> {
529        let mut selected_vars = Vec::new();
530
531        // First, handle predefined variables if any
532        if !project_type.suggested_vars.is_empty() {
533            println!("\nšŸ“‹ Let's configure variables for your {} project:", project_type.name);
534
535            let options: Vec<String> = project_type
536                .suggested_vars
537                .iter()
538                .map(|var| {
539                    let required_marker = if var.required { " (required)" } else { "" };
540                    format!("{} - {}{}", var.name, var.description, required_marker)
541                })
542                .collect();
543
544            let defaults: Vec<bool> = project_type.suggested_vars.iter().map(|var| var.required).collect();
545
546            let Some(selections) = MultiSelect::with_theme(&self.theme)
547                .with_prompt("Select variables to configure (Space to toggle, Enter to continue)")
548                .items(&options)
549                .defaults(&defaults)
550                .interact_opt()?
551            else {
552                return Err(EscPressed.into());
553            };
554
555            // Configure values for selected variables
556            if !selections.is_empty() {
557                println!("\nšŸ”§ Configure variable values:");
558                for &idx in &selections {
559                    let var = &project_type.suggested_vars[idx];
560
561                    let value = Input::<String>::with_theme(&self.theme)
562                        .with_prompt(format!("{} ({})", var.name, var.description))
563                        .default(var.example.clone())
564                        .interact()?;
565
566                    selected_vars.push(SelectedVariable {
567                        name: var.name.clone(),
568                        value,
569                        description: var.description.clone(),
570                        required: var.required,
571                    });
572                }
573            }
574        }
575
576        // Always ask about custom environment variables
577        println!("\nāž• Custom Environment Variables");
578        let Some(add_custom) = Confirm::with_theme(&self.theme)
579            .with_prompt("Would you like to add custom environment variables?")
580            .default(true)
581            .interact_opt()?
582        else {
583            return Err(EscPressed.into());
584        };
585
586        if add_custom {
587            loop {
588                println!("\nšŸ“ Add a custom variable:");
589
590                let var_name = Input::<String>::with_theme(&self.theme)
591                    .with_prompt("Variable name (or press Enter to finish)")
592                    .allow_empty(true)
593                    .interact()?;
594
595                if var_name.is_empty() {
596                    break;
597                }
598
599                let description = Input::<String>::with_theme(&self.theme)
600                    .with_prompt("Description")
601                    .default(format!("{var_name} configuration"))
602                    .interact()?;
603
604                let value = Input::<String>::with_theme(&self.theme)
605                    .with_prompt("Value")
606                    .default("your-value-here".to_string())
607                    .interact()?;
608
609                let Some(required) = Confirm::with_theme(&self.theme)
610                    .with_prompt("Is this variable required?")
611                    .default(false)
612                    .interact_opt()?
613                else {
614                    return Err(EscPressed.into());
615                };
616
617                selected_vars.push(SelectedVariable {
618                    name: var_name,
619                    value,
620                    description,
621                    required,
622                });
623
624                let Some(add_more) = Confirm::with_theme(&self.theme)
625                    .with_prompt("Add another custom variable?")
626                    .default(true)
627                    .interact_opt()?
628                else {
629                    return Err(EscPressed.into());
630                };
631
632                if !add_more {
633                    break;
634                }
635            }
636        }
637
638        self.config.selected_vars.clone_from(&selected_vars);
639        Ok(selected_vars)
640    }
641
642    fn create_and_configure_profiles(
643        &self,
644        project_type: &ProjectType,
645        selected_vars: &[SelectedVariable],
646    ) -> Result<(Vec<String>, HashMap<String, HashMap<String, String>>)> {
647        println!("\nšŸ“ Let's create environment profiles:");
648
649        let mut profiles = Vec::new();
650        let mut profile_configs = HashMap::new();
651
652        // Show suggested profiles
653        let suggested = &project_type.suggested_profiles;
654        let mut options: Vec<String> = suggested
655            .iter()
656            .map(|p| {
657                format!(
658                    "{} ({})",
659                    p,
660                    match p.as_str() {
661                        "development" => "local development",
662                        "testing" => "running tests",
663                        "staging" => "pre-production",
664                        "production" => "live environment",
665                        _ => "custom",
666                    }
667                )
668            })
669            .collect();
670
671        options.push("Add custom profile".to_string());
672
673        let defaults: Vec<bool> = vec![true, false, false, false]; // Default to dev only
674
675        let Some(selections) = MultiSelect::with_theme(&self.theme)
676            .with_prompt("Select profiles to create")
677            .items(&options)
678            .defaults(&defaults)
679            .interact_opt()?
680        else {
681            return Err(EscPressed.into());
682        };
683
684        // Process selections
685        for &idx in &selections {
686            if idx < suggested.len() {
687                profiles.push(suggested[idx].clone());
688            } else if idx == options.len() - 1 {
689                // Add custom profile
690                let custom_name = Input::<String>::with_theme(&self.theme)
691                    .with_prompt("Enter custom profile name")
692                    .interact()?;
693
694                if !custom_name.is_empty() {
695                    profiles.push(custom_name);
696                }
697            }
698        }
699
700        // Allow adding more custom profiles
701        loop {
702            let Some(add_more) = Confirm::with_theme(&self.theme)
703                .with_prompt("Add another custom profile?")
704                .default(false)
705                .interact_opt()?
706            else {
707                return Err(EscPressed.into());
708            };
709
710            if !add_more {
711                break;
712            }
713
714            let custom_name = Input::<String>::with_theme(&self.theme)
715                .with_prompt("Enter profile name")
716                .interact()?;
717
718            if !custom_name.is_empty() && !profiles.contains(&custom_name) {
719                profiles.push(custom_name);
720            }
721        }
722
723        // Configure each profile
724        for profile in &profiles {
725            println!("\nāš™ļø  Configuring '{profile}' profile:");
726            let mut profile_config = HashMap::new();
727
728            // Add selected variables to each profile with profile-specific values
729            for var in selected_vars {
730                let default_value = Self::get_profile_default_value(profile, &var.name, &var.value);
731
732                let value = Input::<String>::with_theme(&self.theme)
733                    .with_prompt(format!("  {}", var.name))
734                    .default(default_value)
735                    .interact()?;
736
737                profile_config.insert(var.name.clone(), value);
738            }
739
740            profile_configs.insert(profile.clone(), profile_config);
741        }
742
743        Ok((profiles, profile_configs))
744    }
745
746    fn get_profile_default_value(profile: &str, var_name: &str, base_value: &str) -> String {
747        match (profile, var_name) {
748            ("development", "NODE_ENV") => "development".to_string(),
749            ("testing", "NODE_ENV") => "test".to_string(),
750            ("staging", "NODE_ENV") => "staging".to_string(),
751            ("production", "NODE_ENV") => "production".to_string(),
752
753            ("development", "DATABASE_URL") => base_value.replace("myapp", "myapp_dev"),
754            ("testing", "DATABASE_URL") => base_value.replace("myapp", "myapp_test"),
755            ("staging", "DATABASE_URL") => base_value.replace("myapp", "myapp_staging"),
756
757            ("development", "LOG_LEVEL") => "debug".to_string(),
758            ("testing", "LOG_LEVEL") => "error".to_string(),
759            ("production", "LOG_LEVEL") => "info".to_string(),
760
761            ("development", "DEBUG") => "true".to_string(),
762            (_, "DEBUG") => "false".to_string(),
763
764            _ => base_value.to_string(),
765        }
766    }
767
768    /// Scans for existing environment files in the current directory.
769    ///
770    /// # Errors
771    ///
772    /// Returns an error if:
773    /// - File system operations fail during scanning
774    /// - User interaction fails (e.g., terminal issues)
775    /// - User cancels the operation (ESC key)
776    pub fn scan_existing_files(&self) -> Result<Option<Vec<PathBuf>>> {
777        println!("\nšŸ” Scanning for existing environment files...");
778
779        let patterns = vec![".env", ".env.*", "docker-compose.yml", "docker-compose.yaml"];
780        let mut found_files = Vec::new();
781
782        for pattern in patterns {
783            if let Ok(paths) = glob(pattern) {
784                for path in paths.flatten() {
785                    found_files.push(path);
786                }
787            }
788        }
789
790        if found_files.is_empty() {
791            return Ok(None);
792        }
793
794        println!("Found existing environment files:");
795        for (i, file) in found_files.iter().enumerate() {
796            let var_count = Self::count_env_vars(file).unwrap_or(0);
797            println!(
798                "  {} {} ({} variables)",
799                if i == 0 { "āœ“" } else { " " },
800                file.display(),
801                var_count
802            );
803        }
804
805        let Some(import) = Confirm::with_theme(&self.theme)
806            .with_prompt("\nWould you like to import these?")
807            .default(true)
808            .interact_opt()?
809        else {
810            return Err(EscPressed.into());
811        };
812
813        if import { Ok(Some(found_files)) } else { Ok(None) }
814    }
815
816    fn count_env_vars(path: &Path) -> Result<usize> {
817        let content = fs::read_to_string(path)?;
818        let count = content
819            .lines()
820            .filter(|line| !line.trim().is_empty() && !line.trim().starts_with('#'))
821            .filter(|line| line.contains('='))
822            .count();
823        Ok(count)
824    }
825
826    /// Imports selected existing environment files based on user choice.
827    ///
828    /// # Errors
829    ///
830    /// Returns an error if:
831    /// - User interaction fails (e.g., terminal issues)
832    /// - User cancels the operation (ESC key)
833    pub fn import_existing(&self, files: Vec<PathBuf>) -> Result<Vec<PathBuf>> {
834        let options: Vec<&str> = vec!["Import all", "Select files to import", "Skip import"];
835
836        let Some(selection) = Select::with_theme(&self.theme)
837            .with_prompt("Import option")
838            .items(&options)
839            .interact_opt()?
840        else {
841            return Err(EscPressed.into());
842        };
843
844        match selection {
845            0 => Ok(files),
846            1 => {
847                let file_names: Vec<String> = files.iter().map(|p| p.display().to_string()).collect();
848
849                let Some(selections) = MultiSelect::with_theme(&self.theme)
850                    .with_prompt("Select files to import")
851                    .items(&file_names)
852                    .interact_opt()?
853                else {
854                    return Err(EscPressed.into());
855                };
856
857                Ok(selections.into_iter().map(|i| files[i].clone()).collect())
858            }
859            _ => Ok(Vec::new()),
860        }
861    }
862
863    /// Configures team collaboration features for the project.
864    ///
865    /// # Errors
866    ///
867    /// Returns an error if:
868    /// - User interaction fails (e.g., terminal issues)
869    /// - User cancels the operation (ESC key)
870    /// - Repository root cannot be found when creating config path
871    pub fn configure_team_features(&self) -> Result<TeamConfig> {
872        println!("\nšŸ‘„ Team Collaboration Setup:");
873
874        let Some(create_config) = Confirm::with_theme(&self.theme)
875            .with_prompt("Create .envx/config.yaml for team?")
876            .default(true)
877            .interact_opt()?
878        else {
879            return Err(EscPressed.into());
880        };
881
882        let git_hooks = false;
883
884        let ci_integration = false;
885
886        let Some(shared_profiles) = Confirm::with_theme(&self.theme)
887            .with_prompt("Enable shared profiles?")
888            .default(true)
889            .interact_opt()?
890        else {
891            return Err(EscPressed.into());
892        };
893
894        let config_path = if create_config {
895            let repo_root = Self::find_repo_root().unwrap_or_else(|_| PathBuf::from("."));
896            repo_root.join(".envx").join("config.yaml")
897        } else {
898            PathBuf::from(".envx/config.yaml")
899        };
900
901        Ok(TeamConfig {
902            config_path,
903            git_hooks,
904            ci_integration,
905            shared_profiles,
906        })
907    }
908
909    fn find_repo_root() -> Result<PathBuf> {
910        let current = std::env::current_dir()?;
911        let mut dir = current.as_path();
912
913        loop {
914            if dir.join(".git").exists() {
915                return Ok(dir.to_path_buf());
916            }
917            match dir.parent() {
918                Some(parent) => dir = parent,
919                None => return Err(eyre!("No git repository found")),
920            }
921        }
922    }
923
924    /// Configures validation rules for environment variables based on user preferences.
925    ///
926    /// # Errors
927    ///
928    /// Returns an error if:
929    /// - User interaction fails (e.g., terminal issues)
930    /// - User cancels the operation (ESC key)
931    /// - Custom pattern configuration fails
932    pub fn configure_validation(&self, project_type: &ProjectType) -> Result<ValidationRules> {
933        println!("\nāœ… Configure Validation Rules:");
934
935        let options = vec![
936            "Require all variables in .envx/config.yaml",
937            "Validate URLs are properly formatted",
938            "Check numeric values are in valid ranges",
939            "Warn about missing required variables",
940            "Strict mode (fail on any validation error)",
941        ];
942
943        let defaults = vec![true, true, true, true, false]; // All except strict mode
944
945        let Some(selections) = MultiSelect::with_theme(&self.theme)
946            .with_prompt("Select validation rules")
947            .items(&options)
948            .defaults(&defaults)
949            .interact_opt()?
950        else {
951            return Err(EscPressed.into());
952        };
953
954        let rules = ValidationRules {
955            require_all_defined: selections.contains(&0),
956            validate_urls: selections.contains(&1),
957            validate_numbers: selections.contains(&2),
958            warn_missing: selections.contains(&3),
959            strict_mode: selections.contains(&4),
960            custom_patterns: self.get_custom_patterns(project_type)?,
961        };
962
963        Ok(rules)
964    }
965
966    fn get_custom_patterns(&self, project_type: &ProjectType) -> Result<HashMap<String, String>> {
967        let mut patterns = HashMap::new();
968
969        match &project_type.category {
970            ProjectCategory::WebApp => {
971                patterns.insert("*_URL".to_string(), r"^https?://.*".to_string());
972                patterns.insert("*_PORT".to_string(), r"^[0-9]{1,5}$".to_string());
973            }
974            ProjectCategory::Docker => {
975                patterns.insert("*_IMAGE".to_string(), r"^[a-z0-9\-_/:.]+$".to_string());
976            }
977            _ => {}
978        }
979
980        let Some(add_custom) = Confirm::with_theme(&self.theme)
981            .with_prompt("\nAdd custom validation pattern?")
982            .default(false)
983            .interact_opt()?
984        else {
985            return Err(EscPressed.into());
986        };
987
988        if add_custom {
989            let pattern_name = Input::<String>::with_theme(&self.theme)
990                .with_prompt("Pattern name (e.g., *_EMAIL)")
991                .interact()?;
992
993            let pattern_regex = Input::<String>::with_theme(&self.theme)
994                .with_prompt("Regex pattern")
995                .interact()?;
996
997            patterns.insert(pattern_name, pattern_regex);
998        }
999
1000        Ok(patterns)
1001    }
1002
1003    /// Reviews the setup configuration with the user and applies it if confirmed.
1004    ///
1005    /// # Errors
1006    ///
1007    /// Returns an error if:
1008    /// - User interaction fails (e.g., terminal issues)
1009    /// - User cancels the operation (ESC key)
1010    /// - Configuration application fails
1011    /// - File I/O operations fail during setup
1012    pub fn review_and_apply(&self, result: &SetupResult) -> Result<()> {
1013        println!("\nšŸ“‹ Setup Summary:");
1014        println!("{}", "━".repeat(50));
1015        println!("Project Type:     {}", result.project_type.name);
1016        println!("Profiles:         {}", result.profiles.join(", "));
1017        println!("Variables:        {} configured", result.selected_vars.len());
1018        println!(
1019            "Create .env files: {}",
1020            if result.create_env_files { "Yes" } else { "No" }
1021        );
1022        println!(
1023            "Team Setup:       {}",
1024            if result.team_config.is_some() {
1025                "Enabled"
1026            } else {
1027                "Disabled"
1028            }
1029        );
1030
1031        if !result.imported_files.is_empty() {
1032            println!("Imported Files:   {}", result.imported_files.len());
1033        }
1034
1035        println!("{}", "━".repeat(50));
1036
1037        let Some(confirm) = Confirm::with_theme(&self.theme)
1038            .with_prompt("\nReady to apply configuration?")
1039            .default(true)
1040            .interact_opt()?
1041        else {
1042            return Err(EscPressed.into());
1043        };
1044
1045        if !confirm {
1046            return Err(eyre!("Setup cancelled by user"));
1047        }
1048
1049        // Apply the configuration
1050        self.apply_configuration(result)?;
1051
1052        Ok(())
1053    }
1054
1055    #[allow(clippy::too_many_lines)]
1056    fn apply_configuration(&self, result: &SetupResult) -> Result<()> {
1057        println!("\nšŸš€ Applying configuration...");
1058
1059        // Create project config
1060        if let Some(team_config) = &result.team_config {
1061            Self::create_project_config(result, &team_config.config_path)?;
1062            println!("āœ“ Created project configuration");
1063        }
1064
1065        // Import files
1066        for file in &result.imported_files {
1067            println!("āœ“ Imported {}", file.display());
1068            // Actual import logic would go here
1069        }
1070
1071        // Create profiles in ProfileManager
1072        let mut profile_manager = ProfileManager::new()?;
1073
1074        // Check for existing profiles and handle conflicts
1075        let mut profile_mappings: HashMap<String, String> = HashMap::new();
1076        for profile_name in &result.profiles {
1077            profile_mappings.insert(profile_name.clone(), profile_name.clone());
1078        }
1079
1080        for (profile_name, _) in &result.profile_configs {
1081            if profile_manager.get(profile_name).is_some() {
1082                println!("\nāš ļø  Profile '{profile_name}' already exists!");
1083
1084                let options = vec![
1085                    format!("Rename new profile (current: {})", profile_name),
1086                    format!("Delete existing '{}' profile and replace", profile_name),
1087                    "Skip this profile".to_string(),
1088                ];
1089
1090                let Some(choice) = Select::with_theme(&self.theme)
1091                    .with_prompt("How would you like to proceed?")
1092                    .items(&options)
1093                    .interact_opt()?
1094                else {
1095                    return Err(EscPressed.into());
1096                };
1097
1098                match choice {
1099                    0 => {
1100                        // Rename new profile
1101                        loop {
1102                            let new_name = Input::<String>::with_theme(&self.theme)
1103                                .with_prompt("Enter new profile name")
1104                                .default(format!("{profile_name}_new"))
1105                                .interact()?;
1106
1107                            if new_name.is_empty() {
1108                                println!("Profile name cannot be empty!");
1109                                continue;
1110                            }
1111
1112                            if profile_manager.get(&new_name).is_none() {
1113                                profile_mappings.insert(profile_name.clone(), new_name);
1114                                break;
1115                            }
1116                            println!("Profile '{new_name}' also exists! Please choose another name.");
1117                        }
1118                    }
1119                    1 => {
1120                        // Delete existing profile
1121                        let Some(confirm_delete) = Confirm::with_theme(&self.theme)
1122                            .with_prompt(format!(
1123                                "Are you sure you want to delete the existing '{profile_name}' profile?"
1124                            ))
1125                            .default(false)
1126                            .interact_opt()?
1127                        else {
1128                            return Err(EscPressed.into());
1129                        };
1130
1131                        if confirm_delete {
1132                            profile_manager.delete(profile_name)?;
1133                            println!("āœ“ Deleted existing profile: {profile_name}");
1134                        } else {
1135                            println!("Skipping profile: {profile_name}");
1136                            profile_mappings.remove(profile_name);
1137                        }
1138                    }
1139                    2 => {
1140                        // Skip this profile
1141                        println!("Skipping profile: {profile_name}");
1142                        profile_mappings.remove(profile_name);
1143                    }
1144                    _ => unreachable!(),
1145                }
1146            }
1147        }
1148
1149        // Create profiles with resolved names
1150        for (original_name, actual_name) in &profile_mappings {
1151            if let Some(profile_vars) = result.profile_configs.get(original_name) {
1152                // Create the profile
1153                profile_manager.create(actual_name.clone(), Some(format!("{actual_name} environment")))?;
1154
1155                // Add variables to the profile
1156                if let Some(profile) = profile_manager.get_mut(actual_name) {
1157                    for (var_name, var_value) in profile_vars {
1158                        profile.add_var(var_name.clone(), var_value.clone(), false);
1159                    }
1160                }
1161
1162                if original_name == actual_name {
1163                    println!("āœ“ Created profile: {actual_name}");
1164                } else {
1165                    println!("āœ“ Created profile: {actual_name} (renamed from {original_name})");
1166                }
1167            }
1168        }
1169
1170        // Set the first profile as active (typically "development")
1171        // Use the mapped name in case it was renamed
1172        if let Some(first_profile) = result.profiles.first() {
1173            if let Some(actual_name) = profile_mappings.get(first_profile) {
1174                profile_manager.switch(actual_name)?;
1175                println!("āœ“ Set active profile: {actual_name}");
1176            }
1177        }
1178
1179        // Create .env files if requested
1180        if result.create_env_files {
1181            Self::create_env_files_with_mappings(result, &profile_mappings)?;
1182        }
1183
1184        // Set environment variables in the current session
1185        for var in &result.selected_vars {
1186            unsafe { std::env::set_var(&var.name, &var.value) };
1187            println!("āœ“ Set {} in current session", var.name);
1188        }
1189
1190        Ok(())
1191    }
1192
1193    fn create_env_files_with_mappings(result: &SetupResult, mappings: &HashMap<String, String>) -> Result<()> {
1194        println!("\nšŸ“ Creating .env files...");
1195
1196        for (original_name, config) in &result.profile_configs {
1197            if let Some(actual_name) = mappings.get(original_name) {
1198                let filename = if actual_name == "development" {
1199                    ".env".to_string()
1200                } else {
1201                    format!(".env.{actual_name}")
1202                };
1203
1204                let mut content = String::new();
1205                content.push_str(&format!("# Environment variables for {actual_name} profile\n"));
1206                if original_name != actual_name {
1207                    content.push_str(&format!("# (originally configured as {original_name})\n"));
1208                }
1209                content.push_str(&format!(
1210                    "# Generated by envx on {}\n\n",
1211                    chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
1212                ));
1213
1214                for (key, value) in config {
1215                    content.push_str(&format!("{key}={value}\n"));
1216                }
1217
1218                fs::write(&filename, content)?;
1219                println!("āœ“ Created {filename}");
1220            }
1221        }
1222
1223        Ok(())
1224    }
1225
1226    fn check_required_variables(result: &SetupResult) {
1227        println!("\nšŸ” Checking environment variables...");
1228
1229        let mut required_set = Vec::new();
1230        let mut required_missing = Vec::new();
1231        let mut optional_set = Vec::new();
1232        let mut optional_missing = Vec::new();
1233
1234        for var in &result.selected_vars {
1235            match std::env::var(&var.name) {
1236                Ok(value) => {
1237                    if value == var.value {
1238                        if var.required {
1239                            required_set.push(&var.name);
1240                        } else {
1241                            optional_set.push(&var.name);
1242                        }
1243                    } else if var.required {
1244                        required_missing.push(&var.name);
1245                    } else {
1246                        optional_missing.push(&var.name);
1247                    }
1248                }
1249                Err(_) => {
1250                    if var.required {
1251                        required_missing.push(&var.name);
1252                    } else {
1253                        optional_missing.push(&var.name);
1254                    }
1255                }
1256            }
1257        }
1258
1259        // Show all set variables
1260        if !required_set.is_empty() || !optional_set.is_empty() {
1261            println!("\nāœ… Successfully set in current session:");
1262            for var in required_set {
1263                println!("   āœ“ {var} (required)");
1264            }
1265            for var in optional_set {
1266                println!("   āœ“ {var}");
1267            }
1268        }
1269
1270        // Show missing required variables
1271        if !required_missing.is_empty() {
1272            println!("\nāŒ Missing required variables:");
1273            for var in &required_missing {
1274                println!("   • {}", *var);
1275            }
1276
1277            println!("\nšŸ’” To apply these variables:");
1278            println!("   1. Close and restart your terminal");
1279            println!("   2. Run 'envx list' to verify they are set");
1280            println!("   3. Or source the .env file: source .env");
1281        }
1282
1283        // Show missing optional variables (as a warning)
1284        if !optional_missing.is_empty() {
1285            println!("\nāš ļø  Optional variables not set in current session:");
1286            for var in optional_missing {
1287                println!("   • {var}");
1288            }
1289            println!("   These are optional and can be set later if needed.");
1290        }
1291
1292        // Overall status message
1293        if required_missing.is_empty() && !result.selected_vars.iter().any(|v| v.required) {
1294            println!("\nāœ… All variables have been configured!");
1295        } else if required_missing.is_empty() {
1296            println!("\nāœ… All required variables are set!");
1297        }
1298
1299        println!("\nāœ… Setup complete! Here's what to do next:");
1300        println!("\n  1. Run `envx list` to see your environment variables");
1301        println!("  2. Run `envx tui` to launch the interactive interface");
1302        println!("  3. Run `envx profile list` to see available profiles");
1303        println!("  4. Run `envx profile set <name>` to switch profiles");
1304
1305        if result.team_config.is_some() {
1306            println!("  5. Commit .envx/config.yaml to share with your team");
1307        }
1308    }
1309
1310    fn create_project_config(result: &SetupResult, path: &Path) -> Result<()> {
1311        // Create directory if needed
1312        if let Some(parent) = path.parent() {
1313            fs::create_dir_all(parent)?;
1314        }
1315
1316        let config = ProjectConfig {
1317            name: Some(result.project_type.name.to_lowercase().replace(' ', "-")),
1318            description: Some(format!("{} project", result.project_type.name)),
1319            required: result
1320                .selected_vars
1321                .iter()
1322                .filter(|v| v.required)
1323                .map(|v| RequiredVar {
1324                    name: v.name.clone(),
1325                    description: Some(v.description.clone()),
1326                    pattern: None,
1327                    example: Some(v.value.clone()),
1328                })
1329                .collect(),
1330            defaults: result
1331                .selected_vars
1332                .iter()
1333                .map(|v| (v.name.clone(), v.value.clone()))
1334                .collect(),
1335            auto_load: vec![".env".to_string(), ".env.local".to_string()],
1336            profile: result.profiles.first().cloned(),
1337            scripts: HashMap::new(),
1338            validation: ConfigValidationRules {
1339                warn_unused: result.validation_rules.warn_missing,
1340                strict_names: result.validation_rules.strict_mode,
1341                patterns: result.validation_rules.custom_patterns.clone(),
1342            },
1343            inherit: true,
1344        };
1345
1346        let yaml = serde_yaml::to_string(&config)?;
1347        fs::write(path, yaml)?;
1348
1349        Ok(())
1350    }
1351
1352    // ... rest of the project type creation methods remain the same ...
1353    fn create_web_app_type() -> ProjectType {
1354        ProjectType {
1355            name: "Web Application".to_string(),
1356            category: ProjectCategory::WebApp,
1357            suggested_vars: vec![
1358                SuggestedVariable {
1359                    name: "NODE_ENV".to_string(),
1360                    description: "Application environment".to_string(),
1361                    example: "development".to_string(),
1362                    required: true,
1363                    sensitive: false,
1364                },
1365                SuggestedVariable {
1366                    name: "PORT".to_string(),
1367                    description: "Server port".to_string(),
1368                    example: "3000".to_string(),
1369                    required: true,
1370                    sensitive: false,
1371                },
1372                SuggestedVariable {
1373                    name: "DATABASE_URL".to_string(),
1374                    description: "Database connection string".to_string(),
1375                    example: "postgresql://localhost:5432/myapp".to_string(),
1376                    required: true,
1377                    sensitive: true,
1378                },
1379                SuggestedVariable {
1380                    name: "JWT_SECRET".to_string(),
1381                    description: "JWT signing secret".to_string(),
1382                    example: "your-secret-key".to_string(),
1383                    required: false,
1384                    sensitive: true,
1385                },
1386                SuggestedVariable {
1387                    name: "API_KEY".to_string(),
1388                    description: "External API key".to_string(),
1389                    example: "your-api-key".to_string(),
1390                    required: false,
1391                    sensitive: true,
1392                },
1393            ],
1394            suggested_profiles: vec![
1395                "development".to_string(),
1396                "testing".to_string(),
1397                "production".to_string(),
1398            ],
1399        }
1400    }
1401
1402    fn create_python_type() -> ProjectType {
1403        ProjectType {
1404            name: "Python Application".to_string(),
1405            category: ProjectCategory::Python,
1406            suggested_vars: vec![
1407                SuggestedVariable {
1408                    name: "PYTHONPATH".to_string(),
1409                    description: "Python module search path".to_string(),
1410                    example: "./src".to_string(),
1411                    required: false,
1412                    sensitive: false,
1413                },
1414                SuggestedVariable {
1415                    name: "DATABASE_URL".to_string(),
1416                    description: "Database connection string".to_string(),
1417                    example: "postgresql://localhost:5432/myapp".to_string(),
1418                    required: true,
1419                    sensitive: true,
1420                },
1421                SuggestedVariable {
1422                    name: "SECRET_KEY".to_string(),
1423                    description: "Django/Flask secret key".to_string(),
1424                    example: "your-secret-key".to_string(),
1425                    required: true,
1426                    sensitive: true,
1427                },
1428                SuggestedVariable {
1429                    name: "DEBUG".to_string(),
1430                    description: "Debug mode flag".to_string(),
1431                    example: "True".to_string(),
1432                    required: false,
1433                    sensitive: false,
1434                },
1435            ],
1436            suggested_profiles: vec![
1437                "development".to_string(),
1438                "testing".to_string(),
1439                "production".to_string(),
1440            ],
1441        }
1442    }
1443
1444    fn create_rust_type() -> ProjectType {
1445        ProjectType {
1446            name: "Rust Application".to_string(),
1447            category: ProjectCategory::Rust,
1448            suggested_vars: vec![
1449                SuggestedVariable {
1450                    name: "RUST_LOG".to_string(),
1451                    description: "Rust logging level".to_string(),
1452                    example: "info".to_string(),
1453                    required: false,
1454                    sensitive: false,
1455                },
1456                SuggestedVariable {
1457                    name: "DATABASE_URL".to_string(),
1458                    description: "Database connection string".to_string(),
1459                    example: "postgresql://localhost:5432/myapp".to_string(),
1460                    required: true,
1461                    sensitive: true,
1462                },
1463                SuggestedVariable {
1464                    name: "SERVER_PORT".to_string(),
1465                    description: "Server port".to_string(),
1466                    example: "8080".to_string(),
1467                    required: true,
1468                    sensitive: false,
1469                },
1470            ],
1471            suggested_profiles: vec!["development".to_string(), "release".to_string()],
1472        }
1473    }
1474
1475    fn create_docker_type() -> ProjectType {
1476        ProjectType {
1477            name: "Docker Application".to_string(),
1478            category: ProjectCategory::Docker,
1479            suggested_vars: vec![
1480                SuggestedVariable {
1481                    name: "COMPOSE_PROJECT_NAME".to_string(),
1482                    description: "Docker Compose project name".to_string(),
1483                    example: "myapp".to_string(),
1484                    required: true,
1485                    sensitive: false,
1486                },
1487                SuggestedVariable {
1488                    name: "DOCKER_REGISTRY".to_string(),
1489                    description: "Docker registry URL".to_string(),
1490                    example: "docker.io".to_string(),
1491                    required: false,
1492                    sensitive: false,
1493                },
1494            ],
1495            suggested_profiles: vec!["local".to_string(), "staging".to_string(), "production".to_string()],
1496        }
1497    }
1498
1499    fn create_microservices_type() -> ProjectType {
1500        ProjectType {
1501            name: "Microservices".to_string(),
1502            category: ProjectCategory::Microservices,
1503            suggested_vars: vec![
1504                SuggestedVariable {
1505                    name: "SERVICE_DISCOVERY_URL".to_string(),
1506                    description: "Service discovery endpoint".to_string(),
1507                    example: "http://consul:8500".to_string(),
1508                    required: true,
1509                    sensitive: false,
1510                },
1511                SuggestedVariable {
1512                    name: "KAFKA_BROKERS".to_string(),
1513                    description: "Kafka broker addresses".to_string(),
1514                    example: "kafka1:9092,kafka2:9092".to_string(),
1515                    required: false,
1516                    sensitive: false,
1517                },
1518            ],
1519            suggested_profiles: vec!["local".to_string(), "kubernetes".to_string(), "production".to_string()],
1520        }
1521    }
1522
1523    fn create_custom_type(&self) -> Result<ProjectType> {
1524        let value = Input::<String>::with_theme(&self.theme)
1525            .with_prompt("Enter project type name")
1526            .default("Custom Project".to_string())
1527            .interact()?;
1528        let name = value;
1529
1530        // For custom projects, we'll skip the predefined variables
1531        // and let the user add all variables as custom
1532        Ok(ProjectType {
1533            name,
1534            category: ProjectCategory::Custom,
1535            suggested_vars: Vec::new(), // Empty, so user can add all as custom
1536            suggested_profiles: vec!["development".to_string(), "production".to_string()],
1537        })
1538    }
1539
1540    fn create_nextjs_type() -> ProjectType {
1541        ProjectType {
1542            name: "Next.js Full-Stack".to_string(),
1543            category: ProjectCategory::NextJs,
1544            suggested_vars: vec![
1545                SuggestedVariable {
1546                    name: "NEXT_PUBLIC_API_URL".to_string(),
1547                    description: "Public API URL".to_string(),
1548                    example: "http://localhost:3000/api".to_string(),
1549                    required: true,
1550                    sensitive: false,
1551                },
1552                SuggestedVariable {
1553                    name: "DATABASE_URL".to_string(),
1554                    description: "Database connection string".to_string(),
1555                    example: "postgresql://localhost:5432/myapp".to_string(),
1556                    required: true,
1557                    sensitive: true,
1558                },
1559                SuggestedVariable {
1560                    name: "NEXTAUTH_SECRET".to_string(),
1561                    description: "NextAuth.js secret".to_string(),
1562                    example: "your-secret-key".to_string(),
1563                    required: true,
1564                    sensitive: true,
1565                },
1566                SuggestedVariable {
1567                    name: "NEXTAUTH_URL".to_string(),
1568                    description: "NextAuth callback URL".to_string(),
1569                    example: "http://localhost:3000".to_string(),
1570                    required: true,
1571                    sensitive: false,
1572                },
1573                SuggestedVariable {
1574                    name: "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY".to_string(),
1575                    description: "Stripe publishable key".to_string(),
1576                    example: "pk_test_...".to_string(),
1577                    required: false,
1578                    sensitive: false,
1579                },
1580            ],
1581            suggested_profiles: vec![
1582                "development".to_string(),
1583                "preview".to_string(),
1584                "production".to_string(),
1585            ],
1586        }
1587    }
1588    fn create_go_type() -> ProjectType {
1589        ProjectType {
1590            name: "Go Application".to_string(),
1591            category: ProjectCategory::Go,
1592            suggested_vars: vec![
1593                SuggestedVariable {
1594                    name: "GO_ENV".to_string(),
1595                    description: "Go environment".to_string(),
1596                    example: "development".to_string(),
1597                    required: true,
1598                    sensitive: false,
1599                },
1600                SuggestedVariable {
1601                    name: "DATABASE_URL".to_string(),
1602                    description: "Database connection string".to_string(),
1603                    example: "postgres://localhost:5432/myapp".to_string(),
1604                    required: true,
1605                    sensitive: true,
1606                },
1607                SuggestedVariable {
1608                    name: "REDIS_URL".to_string(),
1609                    description: "Redis connection URL".to_string(),
1610                    example: "redis://localhost:6379".to_string(),
1611                    required: false,
1612                    sensitive: false,
1613                },
1614                SuggestedVariable {
1615                    name: "JWT_SECRET".to_string(),
1616                    description: "JWT signing secret".to_string(),
1617                    example: "your-secret-key".to_string(),
1618                    required: true,
1619                    sensitive: true,
1620                },
1621                SuggestedVariable {
1622                    name: "LOG_LEVEL".to_string(),
1623                    description: "Logging level".to_string(),
1624                    example: "info".to_string(),
1625                    required: false,
1626                    sensitive: false,
1627                },
1628            ],
1629            suggested_profiles: vec![
1630                "development".to_string(),
1631                "testing".to_string(),
1632                "production".to_string(),
1633            ],
1634        }
1635    }
1636}