Skip to main content

chamber_ui/
app.rs

1use crate::app;
2use crate::vault_selector::{VaultAction, VaultSelector, VaultSelectorMode};
3use async_trait::async_trait;
4use chamber_import_export::{ExportFormat, export_items, import_items};
5use chamber_password_gen::PasswordConfig;
6use chamber_vault::{AutoLockCallback, AutoLockConfig, AutoLockService, Item, ItemKind, NewItem, Vault, VaultManager};
7use color_eyre::Result;
8use color_eyre::eyre::eyre;
9use ratatui::prelude::Style;
10use ratatui::style::Color;
11use std::path::PathBuf;
12use std::sync::Arc;
13use tui_textarea::TextArea;
14#[derive(Clone, Copy, PartialEq, Eq)]
15pub enum Screen {
16    Unlock,
17    Main,
18    AddItem,
19    ViewItem,
20    EditItem,
21    ChangeMaster,
22    GeneratePassword,
23    ImportExport,
24    VaultSelector,
25}
26
27#[derive(Clone, Copy, PartialEq, Eq)]
28pub enum UnlockField {
29    Master,
30    Confirm,
31}
32
33#[derive(Clone, Copy, PartialEq, Eq)]
34pub enum ChangeKeyField {
35    Current,
36    New,
37    Confirm,
38}
39
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub enum AddItemField {
42    Name,
43    Kind,
44    Value,
45}
46
47#[derive(Clone, Copy, PartialEq, Eq)]
48pub enum PasswordGenField {
49    Length,
50    Options,
51    Generate,
52}
53
54#[derive(Clone, Copy, PartialEq, Eq)]
55pub enum ImportExportField {
56    Path,
57    Format,
58    Action,
59}
60
61#[derive(Clone, Copy, PartialEq, Eq)]
62pub enum ImportExportMode {
63    Export,
64    Import,
65}
66
67#[allow(dead_code)]
68#[derive(Clone, Copy, PartialEq, Eq)]
69pub enum ViewMode {
70    All,
71    Passwords,
72    Environment,
73    Notes,
74}
75
76impl ViewMode {
77    pub const fn as_str(self) -> &'static str {
78        match self {
79            ViewMode::All => "Items",
80            ViewMode::Passwords => "Passwords",
81            ViewMode::Environment => "Environment",
82            ViewMode::Notes => "Notes",
83        }
84    }
85}
86
87#[derive(Clone, Copy, PartialEq, Eq)]
88pub enum StatusType {
89    Info,
90    Success,
91    Warning,
92    Error,
93}
94
95#[derive(Debug, Clone)]
96pub struct ItemCounts {
97    pub total: usize,
98    pub passwords: usize,
99    pub env_vars: usize,
100    pub notes: usize,
101    pub api_keys: usize,
102    pub ssh_keys: usize,
103    pub certificates: usize,
104    pub databases: usize,
105    pub credit_cards: usize,
106    pub secure_notes: usize,
107    pub identities: usize,
108    pub servers: usize,
109    pub wifi_passwords: usize,
110    pub licenses: usize,
111    pub bank_accounts: usize,
112    pub documents: usize,
113    pub recovery_codes: usize,
114    pub oauth_tokens: usize,
115}
116
117#[derive(Debug, Clone)]
118pub struct CountdownInfo {
119    pub enabled: bool,
120    pub minutes_left: i64,
121    pub seconds_left: i64,
122}
123
124pub struct TuiAutoLockCallback {
125    // Callback to lock the TUI app
126}
127
128#[async_trait]
129impl AutoLockCallback for TuiAutoLockCallback {
130    async fn on_auto_lock(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
131        // Lock the vault in TUI mode
132        // This could set a flag that the main loop checks
133        Ok(())
134    }
135}
136
137#[allow(clippy::struct_excessive_bools)]
138pub struct App {
139    pub vault: Vault,
140    pub vault_manager: VaultManager,
141    pub vault_selector: VaultSelector,
142
143    pub screen: Screen,
144    pub master_input: String,
145    pub master_confirm_input: String,
146    pub master_mode_is_setup: bool,
147    pub unlock_focus: UnlockField,
148    pub error: Option<String>,
149
150    pub items: Vec<Item>,
151    pub selected: usize,
152    pub view_mode: ViewMode,
153    pub filtered_items: Vec<Item>,
154    pub search_query: String,
155    pub search_mode: bool,
156
157    pub add_name: String,
158    pub add_kind_idx: usize,
159    pub add_value: String,
160    pub add_value_scroll: usize,
161    pub status_message: Option<String>,
162    pub status_type: StatusType,
163    pub scroll_offset: usize,
164    pub add_value_textarea: TextArea<'static>,
165    pub auto_lock_service: Option<Arc<AutoLockService>>,
166    pub auto_locked: bool,
167    pub countdown_info: Option<CountdownInfo>,
168
169    // Change passes key dialog fields
170    pub ck_current: String,
171    pub ck_new: String,
172    pub ck_confirm: String,
173    pub ck_focus: ChangeKeyField,
174    pub add_focus: AddItemField,
175    pub view_item: Option<Item>,
176    pub view_show_value: bool,
177    pub edit_item: Option<Item>,
178    pub edit_value: String,
179
180    // Password generation fields
181    pub gen_focus: PasswordGenField,
182    pub gen_length_str: String,
183    pub gen_config: PasswordConfig,
184    pub generated_password: Option<String>,
185
186    // Import/Export fields
187    pub ie_focus: ImportExportField,
188    pub ie_mode: ImportExportMode,
189    pub ie_path: String,
190    pub ie_format_idx: usize,
191    pub ie_formats: Vec<&'static str>,
192}
193
194impl App {
195    /// Initializes a new instance of the struct.
196    ///
197    /// This function creates or opens a vault, determines whether the master mode setup
198    /// is required, and initializes the various fields required for managing the application state.
199    ///
200    /// # Returns
201    /// - `Result<Self>`: A `Result` containing the initialized struct instance on success,
202    ///   or an error if the vault fails to open or create.
203    ///
204    /// # Fields
205    /// - `vault`: Handles secure storage by opening or creating a vault.
206    /// - `screen`: Represents the current active screen, initialized to the `Unlock` screen.
207    /// - `master_input`: Stores user input for the master password during setup or unlock phase.
208    /// - `master_confirm_input`: Stores user input for confirming the master password during setup.
209    /// - `master_mode_is_setup`: Indicates whether the master mode is set up (false if initialization is incomplete).
210    /// - `unlock_focus`: Tracks which unlock field is currently focused (e.g., Master field).
211    /// - `error`: Holds any error message or state, defaulted to `None`.
212    /// - `items`: A vector holding all items stored in the vault.
213    /// - `selected`: Tracks the index of the currently selected item in the items list.
214    /// - `view_mode`: Specifies the current filter/view mode for items (e.g., All items).
215    /// - `filtered_items`: A vector holding the subset of items that match the current search query or filter.
216    /// - `search_query`: Stores the user's current search input or query.
217    /// - `add_name`: Field for the name of an item to be added.
218    /// - `add_kind_idx`: Indicates the index of the kind/type of the item being added.
219    /// - `add_value`: The value of the item being added.
220    /// - `add_value_scroll`: Tracks the scroll state for long values when adding an item.
221    /// - `status_message`: Holds transient status messages to display to the user.
222    /// - `status_type`: Indicates the type of status message (e.g., Info, Warning, Error).
223    ///
224    /// # Change Key Fields
225    /// - `ck_current`: Stores the current master key value.
226    /// - `ck_new`: Stores the new master key value.
227    /// - `ck_confirm`: Confirms the new master key value.
228    /// - `ck_focus`: Tracks which field is focused during the change key process.
229    ///
230    /// # Add Item Fields
231    /// - `add_focus`: Tracks which field is focused when adding a new item (e.g., Name).
232    ///
233    /// # Viewing and Editing Items
234    /// - `view_item`: The currently selected item for viewing, if any.
235    /// - `view_show_value`: Indicates whether to reveal the value of the viewed item.
236    /// - `edit_item`: The item currently being edited, if any.
237    /// - `edit_value`: The edited value of the currently selected item.
238    ///
239    /// # Password Generation
240    /// - `gen_focus`: The current focus field in the password generation process (e.g., Length).
241    /// - `gen_length_str`: String representation of the desired password length (default: "16").
242    /// - `gen_config`: Configuration settings for password generation (e.g., character set, length).
243    /// - `generated_password`: Holds the last generated password, if any.
244    ///
245    /// # Import/Export
246    /// - `ie_focus`: Tracks which field is focused during import/export operations (e.g., File Path).
247    /// - `ie_mode`: Indicates the mode (Import or Export) for import/export operations.
248    /// - `ie_path`: Stores the file path selected for import/export.
249    /// - `ie_format_idx`: Tracks the index of the currently selected format for import/export.
250    /// - `ie_formats`: A vector containing supported file formats for import/export (e.g., "json", "csv").
251    ///
252    /// # Errors
253    /// Return an error if the vault cannot be opened or created successfully.
254    ///
255    /// # Panics
256    pub fn new() -> Result<Self> {
257        let vault = Vault::open_default()?;
258        let vault_manager = VaultManager::new()?;
259        let vault_selector = VaultSelector::new();
260
261        // Determine initial screen based on vault state
262        let (_, master_mode_is_setup) = if vault.is_initialized() {
263            (Screen::Unlock, false) // Just unlock, no setup
264        } else {
265            (Screen::Unlock, true) // Setup mode (create master password)
266        };
267
268        let auto_lock_config = AutoLockConfig::default();
269        let callback = Arc::new(TuiAutoLockCallback {});
270        let auto_lock_service = Some(Arc::new(AutoLockService::new(auto_lock_config, callback)));
271
272        Ok(Self {
273            vault,
274            vault_manager,
275            vault_selector,
276
277            screen: Screen::Unlock,
278            master_input: String::new(),
279            master_confirm_input: String::new(),
280            master_mode_is_setup,
281            unlock_focus: UnlockField::Master,
282            error: None,
283            items: vec![],
284            selected: 0,
285            view_mode: ViewMode::All,
286            filtered_items: vec![],
287            search_query: String::new(),
288            search_mode: false,
289            add_name: String::new(),
290            add_kind_idx: 0,
291            add_value: String::new(),
292            add_value_scroll: 0,
293            status_message: None,
294            status_type: StatusType::Info,
295            scroll_offset: 0,
296            add_value_textarea: {
297                let mut textarea = TextArea::default();
298                // Enable line numbers
299                textarea.set_line_number_style(Style::default().fg(Color::DarkGray));
300                textarea.set_cursor_line_style(Style::default());
301                // Optional: set a placeholder text
302                textarea.set_placeholder_text("Enter your value here...");
303                textarea
304            },
305            auto_lock_service,
306            auto_locked: false,
307            countdown_info: None,
308
309            ck_current: String::new(),
310            ck_new: String::new(),
311            ck_confirm: String::new(),
312            ck_focus: ChangeKeyField::Current,
313            add_focus: AddItemField::Name,
314            view_item: None,
315            view_show_value: false,
316            edit_item: None,
317            edit_value: String::new(),
318
319            // Initialize password generation fields
320            gen_focus: PasswordGenField::Length,
321            gen_length_str: "16".to_string(),
322            gen_config: PasswordConfig::default(),
323            generated_password: None,
324
325            // Initialize import/export fields
326            ie_focus: ImportExportField::Path,
327            ie_mode: ImportExportMode::Export,
328            ie_path: String::new(),
329            ie_format_idx: 0,
330            ie_formats: vec!["json", "csv", "backup"],
331        })
332    }
333
334    fn validate_master_strength(s: &str) -> Result<()> {
335        if s.len() < 8 {
336            return Err(eyre!("Master key must be at least 8 characters long"));
337        }
338        if !s.chars().any(|c| c.is_ascii_lowercase()) {
339            return Err(eyre!("Master key must contain a lowercase letter"));
340        }
341        if !s.chars().any(|c| c.is_ascii_uppercase()) {
342            return Err(eyre!("Master key must contain an uppercase letter"));
343        }
344        if !s.chars().any(|c| c.is_ascii_digit()) {
345            return Err(eyre!("Master key must contain a digit"));
346        }
347        Ok(())
348    }
349
350    /// Unlocks the application vault using the provided master key and performs necessary validations.
351    ///
352    /// This function checks if the master key setup process is initiated, validates the user input,
353    /// and sets up or unlocks the vault accordingly. In case of errors during validation or unlocking
354    /// operations, appropriate error messages are set.
355    ///
356    /// ## Steps
357    /// 1. If `master_mode_is_setup` is true:
358    ///     - Validate the presence of master input and confirmation input. If either is empty, an error
359    ///       message is set, and the function will return early.
360    ///     - Compare `master_input` and `master_confirm_input`. If they do not match, an error message
361    ///       is set, and the function exits.
362    ///     - Validate the strength of the `master_input` using `validate_master_strength`. If it fails,
363    ///       sets an error message and exits.
364    ///     - Initialize the vault with `master_input` and reset the `master_mode_is_setup` flag to false.
365    /// 2. Unlock the vault with the provided `master_input`. On failure to unlock, sets an error message
366    ///    with the reason and exits with the corresponding error.
367    /// 3. Refresh the items in the application to reflect the unlocked state.
368    /// 4. Set the current screen to `Screen::Main`.
369    /// 5. Clear any existing error messages to indicate successful operation.
370    /// 6. Return `Ok(())` if no errors occurred.
371    ///
372    /// ## Returns
373    /// - `Ok(())` on successful unlocking and initialization of the vault.
374    /// - `Err` with the propagation of error from validation or unlocking operations.
375    ///
376    /// ## Errors
377    /// This function sets the `error` field with one of the following messages on failure:
378    /// - "Please enter and confirm your master key." - if either master input or confirmation input is missing.
379    /// - "Master keys do not match." - if the confirmation of the master key does not match the input.
380    /// - Error message returned by `validate_master_strength` - if the master key is deemed weak or invalid.
381    /// - "Unlock failed: {e}" - if unlocking the vault fails.
382    ///
383    /// ## Side Effects
384    /// - Updates the `error` field in the struct to reflect any issues encountered during execution.
385    /// - Modifies the state of `screen`, `master_mode_is_setup`, and `vault` upon successful execution.
386    pub fn unlock(&mut self) -> Result<()> {
387        if self.master_mode_is_setup {
388            // Setup mode: create new master password (needs confirmation)
389            if self.master_input != self.master_confirm_input {
390                self.error = Some("Passwords do not match".to_string());
391                return Ok(());
392            }
393
394            Self::validate_master_strength(&self.master_input)?;
395
396            // Initialize the vault
397            self.vault.initialize(&self.master_input)?;
398        }
399
400        // Always try to unlock (works for both setup and normal mode)
401        if let Ok(()) = self.vault.unlock(&self.master_input) {
402            self.refresh_items()?;
403            self.screen = Screen::Main;
404            self.error = None;
405            self.master_input.clear();
406            self.master_confirm_input.clear();
407        } else {
408            self.error = Some("Invalid master password".to_string());
409            self.master_input.clear();
410            if self.master_mode_is_setup {
411                self.master_confirm_input.clear();
412            }
413        }
414
415        Ok(())
416    }
417
418    /// Refreshes the list of items and updates the filtered items.
419    ///
420    /// This method performs the following actions:
421    /// 1. Updates the `items` field by retrieving the latest list of items from the `vault`.
422    /// 2. Applies filtering logic to update the `filtered_items` list.
423    /// 3. Ensures that the current selection (`selected`) is within the bounds of the updated `filtered_items` list.
424    ///    If the current selection is out of bounds but the `filtered_items` list is not empty,
425    ///    it adjusts `selected` to the last valid index.
426    ///
427    /// # Errors
428    /// Returns an error if fetching the list of items from the `vault` fails.
429    ///
430    /// # Returns
431    /// - `Ok(())` if the operation is successful.
432    /// - `Err` with the specific error encountered when listing items from the `vault`.
433    pub fn refresh_items(&mut self) -> Result<()> {
434        self.items = self.vault.list_items()?;
435        self.update_filtered_items();
436        if self.selected >= self.filtered_items.len() && !self.filtered_items.is_empty() {
437            self.selected = self.filtered_items.len().saturating_sub(1);
438        }
439        Ok(())
440    }
441
442    pub fn update_filtered_items(&mut self) {
443        let mut filtered = self.items.clone();
444
445        // Apply view mode filter
446        if self.view_mode != ViewMode::All {
447            filtered.retain(|item| match self.view_mode {
448                ViewMode::Passwords => matches!(item.kind, ItemKind::Password),
449                ViewMode::Environment => matches!(item.kind, ItemKind::EnvVar),
450                ViewMode::Notes => matches!(item.kind, ItemKind::Note),
451                ViewMode::All => true,
452            });
453        }
454
455        // Apply search filter
456        if !self.search_query.is_empty() {
457            let query_lower = self.search_query.to_lowercase();
458            filtered.retain(|item| {
459                item.name.to_lowercase().contains(&query_lower) || item.value.to_lowercase().contains(&query_lower)
460            });
461        }
462
463        // Sort by kind first, then by name
464        filtered.sort_by(|a, b| {
465            use std::cmp::Ordering;
466            match a.kind.as_str().cmp(b.kind.as_str()) {
467                Ordering::Equal => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
468                other => other,
469            }
470        });
471
472        self.filtered_items = filtered;
473
474        // Adjust selection if needed
475        if self.selected >= self.filtered_items.len() && !self.filtered_items.is_empty() {
476            self.selected = self.filtered_items.len() - 1;
477        }
478    }
479
480    pub fn get_selected_item(&self) -> Option<&Item> {
481        self.filtered_items.get(self.selected)
482    }
483
484    pub fn get_item_counts(&self) -> ItemCounts {
485        let passwords = self
486            .items
487            .iter()
488            .filter(|i| matches!(i.kind, ItemKind::Password))
489            .count();
490        let env_vars = self.items.iter().filter(|i| matches!(i.kind, ItemKind::EnvVar)).count();
491        let notes = self.items.iter().filter(|i| matches!(i.kind, ItemKind::Note)).count();
492        let api_keys = self.items.iter().filter(|i| matches!(i.kind, ItemKind::ApiKey)).count();
493        let ssh_keys = self.items.iter().filter(|i| matches!(i.kind, ItemKind::SshKey)).count();
494        let certificates = self
495            .items
496            .iter()
497            .filter(|i| matches!(i.kind, ItemKind::Certificate))
498            .count();
499        let databases = self
500            .items
501            .iter()
502            .filter(|i| matches!(i.kind, ItemKind::Database))
503            .count();
504
505        // New categories
506        let credit_cards = self
507            .items
508            .iter()
509            .filter(|i| matches!(i.kind, ItemKind::CreditCard))
510            .count();
511        let secure_notes = self
512            .items
513            .iter()
514            .filter(|i| matches!(i.kind, ItemKind::SecureNote))
515            .count();
516        let identities = self
517            .items
518            .iter()
519            .filter(|i| matches!(i.kind, ItemKind::Identity))
520            .count();
521        let servers = self.items.iter().filter(|i| matches!(i.kind, ItemKind::Server)).count();
522        let wifi_passwords = self
523            .items
524            .iter()
525            .filter(|i| matches!(i.kind, ItemKind::WifiPassword))
526            .count();
527        let licenses = self
528            .items
529            .iter()
530            .filter(|i| matches!(i.kind, ItemKind::License))
531            .count();
532        let bank_accounts = self
533            .items
534            .iter()
535            .filter(|i| matches!(i.kind, ItemKind::BankAccount))
536            .count();
537        let documents = self
538            .items
539            .iter()
540            .filter(|i| matches!(i.kind, ItemKind::Document))
541            .count();
542        let recovery_codes = self
543            .items
544            .iter()
545            .filter(|i| matches!(i.kind, ItemKind::Recovery))
546            .count();
547        let oauth_tokens = self.items.iter().filter(|i| matches!(i.kind, ItemKind::OAuth)).count();
548
549        ItemCounts {
550            total: self.items.len(),
551            passwords,
552            env_vars,
553            notes,
554            api_keys,
555            ssh_keys,
556            certificates,
557            databases,
558            credit_cards,
559            secure_notes,
560            identities,
561            servers,
562            wifi_passwords,
563            licenses,
564            bank_accounts,
565            documents,
566            recovery_codes,
567            oauth_tokens,
568        }
569    }
570
571    /// Adds a new item to the vault with the specified details and updates the UI.
572    ///
573    /// # Description
574    /// This function creates a new item based on the user input, validates it,
575    /// and stores it in the vault. If the operation is successful, the UI is updated
576    /// to reflect the addition and the input fields are reset. If an error occurs,
577    /// appropriate error messages and statuses are set.
578    ///
579    /// # Fields Used
580    /// - `add_kind_idx`: Determines the type of item being added (e.g., `Password`, `EnvVar`, `Note`, etc.).
581    /// - `add_name`: The name of the new item, trimmed of whitespace.
582    /// - `add_value_textarea`: The content or value of the new item, usually multi-line.
583    /// - `vault`: The storage structure which handles item creation.
584    /// - `add_value`: Secondary field for item value, cleared after addition.
585    /// - `add_value_scroll`: Resets the scroll position of the textarea after addition.
586    /// - `screen`: Sets the screen to the main view upon successful addition.
587    /// - `error`: Displays error messages for failed operations.
588    /// - `status`: Updates the user-visible status of the addition operation.
589    ///
590    /// # Process
591    /// 1. Determines the item type (`kind`) based on `add_kind_idx`:
592    ///    - `0` -> Password
593    ///    - `1` -> Environment Variable
594    ///    - `3` -> API Key
595    ///    - `4` -> SSH Key
596    ///    - `5` -> Certificate
597    ///    - `6` -> Database
598    ///    - Default -> Note
599    /// 2. Fetches the item's value from the textarea (`add_value_textarea`), joining multiple lines with `\n`.
600    /// 3. Creates a `NewItem` structure with the gathered data.
601    /// 4. Attempts to add the item using `vault.create_item`.
602    /// 5. Handles responses:
603    ///    - **Success**: Resets input fields, updates item list, switches to the main screen, and displays a success message.
604    ///    - **Failure**: If the name already exists, prompts the user to choose a different name. For other errors, displays a generic error message.
605    ///
606    /// # Returns
607    /// Returns an `Ok(())` on successful completion of the process or propagates an error if any step fails.
608    ///
609    /// # Errors
610    /// - Returns an error if refreshing the items (`refresh_items`) fails.
611    /// - Updates the `error` and `status` fields with detailed context if item creation fails.
612    ///
613    /// # Notes
614    /// - Resets both single-line (`add_value`) and multi-line (`add_value_textarea`) value fields upon successful addition.
615    /// - Automatically trims leading and trailing whitespace from the item name.
616    pub fn add_item(&mut self) -> Result<()> {
617        let kind = ItemKind::all()[self.add_kind_idx.min(ItemKind::all().len() - 1)];
618
619        // Get the value from the textarea instead of add_value
620        let value = self.add_value_textarea.lines().join("\n");
621
622        let new_item = NewItem {
623            name: self.add_name.trim().to_string(),
624            kind,
625            value, // Use the textarea content
626        };
627
628        match self.vault.create_item(&new_item) {
629            Ok(()) => {
630                self.add_name.clear();
631                self.add_value.clear();
632                // Reset the textarea as well
633                self.add_value_textarea = TextArea::default();
634                self.add_value_scroll = 0;
635                self.refresh_items()?;
636                self.screen = Screen::Main;
637                self.error = Some("Item added.".into());
638                self.set_status("Item added successfully.".to_string(), StatusType::Success);
639            }
640            Err(e) => {
641                let msg = e.to_string();
642                if msg.contains("already exists") {
643                    self.error = Some(format!("Item '{}' already exists.", new_item.name));
644                    self.set_status(
645                        format!(
646                            "Item '{}' already exists. Please choose a different name.",
647                            new_item.name
648                        ),
649                        StatusType::Warning,
650                    );
651                } else {
652                    self.error = Some(format!("Failed to add item: {msg}"));
653                    self.set_status(format!("Failed to add item: {msg}"), StatusType::Error);
654                }
655            }
656        }
657        Ok(())
658    }
659
660    /// Deletes the currently selected item from the vault.
661    ///
662    /// This function retrieves the currently selected item, deletes it from the vault
663    /// using its unique identifier, and then refreshes the list of items to reflect the changes.
664    /// If no item is selected, the function does nothing.
665    ///
666    /// # Errors
667    ///
668    /// Returns an error if:
669    /// - Retrieving the selected item fails.
670    /// - Deleting the item from the vault fails.
671    /// - Refreshing the item list fails.
672    pub fn delete_selected(&mut self) -> Result<()> {
673        if let Some(item) = self.get_selected_item() {
674            let item_id = item.id;
675            self.vault.delete_item(item_id)?;
676            self.refresh_items()?;
677        }
678        Ok(())
679    }
680
681    /// Changes the master key for the application if provided inputs meet the necessary conditions.
682    ///
683    /// This function performs several validations to ensure the master key change process is secure:
684    /// 1. It checks if all required input fields (`ck_current`, `ck_new`, and `ck_confirm`) are filled.
685    /// 2. It validates that the new master key (`ck_new`) matches the confirmation key (`ck_confirm`).
686    /// 3. It verifies the strength of the new master key using the `validate_master_strength` method.
687    ///
688    /// If any of these conditions fail, an appropriate error message is stored in the `error` field,
689    /// and the process halts without changing the master key.
690    ///
691    /// Once all validations are passed, the function updates the master key by calling the
692    /// `change_master_key` method of the `vault`. After a successful update, it clears all input fields,
693    /// resets the error message, and navigates back to the main screen.
694    ///
695    /// # Returns
696    ///
697    /// * `Ok(())` - If the master key has been successfully changed or
698    ///   the process ended due to a validation failure without panicking.
699    /// * `Err(Error)` - If an error occurs while attempting to change the key in the `vault`.
700    ///
701    /// # Errors
702    ///
703    /// - If any of the following conditions occur, an error is stored in the `error` field,
704    ///   and the function returns `Ok`:
705    ///   - Any of the required fields (`ck_current`, `ck_new`, or `ck_confirm`) are empty.
706    ///   - The new master key and confirmation key do not match.
707    ///   - The new master key fails the strength validation.
708    /// - If the `vault.change_master_key` method returns an error, it will propagate as a `Result::Err`.
709    pub fn change_master(&mut self) -> Result<()> {
710        if self.ck_current.is_empty() || self.ck_new.is_empty() || self.ck_confirm.is_empty() {
711            self.error = Some("Please fill out all fields.".into());
712            return Ok(());
713        }
714        if self.ck_new != self.ck_confirm {
715            self.error = Some("New master keys do not match.".into());
716            return Ok(());
717        }
718        if let Err(e) = Self::validate_master_strength(&self.ck_new) {
719            self.error = Some(e.to_string());
720            return Ok(());
721        }
722        self.vault.change_master_key(&self.ck_current, &self.ck_new)?;
723        self.ck_current.clear();
724        self.ck_new.clear();
725        self.ck_confirm.clear();
726        self.screen = Screen::Main;
727        self.error = None;
728        Ok(())
729    }
730
731    /// Copies the currently selected item to the clipboard.
732    ///
733    /// This function retrieves the currently selected item using the `get_selected_item` method.
734    /// If an item is selected, it initializes the system clipboard, attempts to copy the selected
735    /// item's value to the clipboard, and updates the status message to indicate success.
736    ///
737    /// # Returns
738    ///
739    /// * `Ok(())` - If the item is successfully copied to the clipboard or no item is selected.
740    /// * `Err(anyhow::Error)` - If there's an error while accessing the clipboard or copying the
741    ///   item to the clipboard.
742    ///
743    /// # Errors
744    ///
745    /// - Returns an error if accessing the clipboard fails.
746    /// - Returns an error if copying the selected item's value to the clipboard fails.
747    ///
748    /// # Behavior
749    ///
750    /// - If no item is selected (`get_selected_item` returns `None`), the function does nothing and
751    ///   returns `Ok(())`.
752    ///
753    /// - If an item is selected (`get_selected_item` returns `Some`), it:
754    ///   - Initializes a new `arboard::Clipboard` instance.
755    ///   - Copies the `value` of the selected item to the clipboard.
756    ///   - Sets a status message indicating that the item has been successfully copied to the clipboard.
757    ///
758    /// # Dependencies
759    ///
760    /// This function relies on the `arboard` crate for clipboard interactions and the `anyhow` crate
761    /// for error handling. It also assumes the existence of the following methods:
762    /// - `get_selected_item`: Retrieves the currently selected item, returning an option.
763    /// - `set_status`: Updates the application's status message and type.
764    ///
765    pub fn copy_selected(&mut self) -> Result<()> {
766        if let Some(item) = self.get_selected_item() {
767            let mut clipboard = arboard::Clipboard::new().map_err(|e| eyre!("Failed to access clipboard: {}", e))?;
768            clipboard
769                .set_text(&item.value)
770                .map_err(|e| eyre!("Failed to copy to clipboard: {}", e))?;
771            self.set_status(format!("Copied '{}' to clipboard", item.name), StatusType::Success);
772        }
773        Ok(())
774    }
775
776    pub fn view_selected(&mut self) {
777        if let Some(item) = self.get_selected_item() {
778            self.view_item = Some(item.clone());
779            self.view_show_value = false;
780            self.screen = Screen::ViewItem;
781        }
782    }
783
784    pub const fn toggle_value_visibility(&mut self) {
785        self.view_show_value = !self.view_show_value;
786    }
787
788    pub fn edit_selected(&mut self) {
789        let selected_item = self.get_selected_item().cloned();
790        if let Some(item) = selected_item {
791            self.edit_item = Some(item.clone());
792            self.edit_value.clone_from(&item.value);
793            self.screen = Screen::EditItem;
794        }
795    }
796
797    /// Attempts to save edits made to an item in the vault and updates the user interface accordingly.
798    ///
799    /// # Behavior
800    /// - If the `edit_value` is empty (after trimming), it sets an error message ("Value cannot be empty") and exits early.
801    /// - Otherwise, it updates the item in the vault identified by `edit_item.id` with the new `edit_value`.
802    /// - After updating the item, it clears the `edit_item` and `edit_value`, refreshes the list of items, and switches
803    ///   the application's screen back to the main screen while clearing any previous errors.
804    ///
805    /// # Errors
806    /// - If updating the vault fails, an error is propagated from the `vault.update_item` method.
807    /// - If refreshing items fails, an error is propagated from the `refresh_items` method.
808    ///
809    /// # Returns
810    /// - `Ok(())` if the edit is successfully saved or if the `edit_value` is empty.
811    /// - `Err` if an error occurs during vault updates or refreshing items.
812    ///
813    /// # Fields/State
814    /// - `self.edit_item`: The item currently being edited. If `None`, the method does nothing.
815    /// - `self.edit_value`: The new value to be saved to the item. If empty (once trimmed), the method sets an error message and exits early.
816    /// - `self.error`: An optional error message for display purposes. This is set if the `edit_value` is empty or cleared on successful operation.
817    /// - `self.vault`: The storage mechanism used to update the item.
818    /// - `self.screen`: Controls the application screen flow. Set to `Screen::Main` after a successful edit.
819    ///
820    pub fn save_edit(&mut self) -> Result<()> {
821        if let Some(item) = &self.edit_item {
822            if self.edit_value.trim().is_empty() {
823                self.error = Some("Value cannot be empty".into());
824                return Ok(());
825            }
826
827            self.vault.update_item(item.id, &self.edit_value)?;
828            self.edit_item = None;
829            self.edit_value.clear();
830            self.refresh_items()?;
831            self.screen = Screen::Main;
832            self.error = None;
833        }
834        Ok(())
835    }
836
837    // Password generation methods
838    pub fn open_password_generator(&mut self) {
839        self.gen_focus = PasswordGenField::Length;
840        self.gen_length_str = self.gen_config.length.to_string();
841        self.generated_password = None;
842        self.error = None;
843        self.screen = Screen::GeneratePassword;
844    }
845
846    pub fn generate_password(&mut self) {
847        if let Ok(length) = self.gen_length_str.parse::<usize>() {
848            self.gen_config.length = length.clamp(4, 128);
849        } else {
850            self.error = Some("Invalid length".into());
851            return;
852        }
853
854        match self.gen_config.generate() {
855            Ok(password) => {
856                self.generated_password = Some(password);
857                self.error = None;
858            }
859            Err(e) => {
860                self.error = Some(format!("Generation failed: {e}"));
861            }
862        }
863    }
864
865    /// Copies the generated password to the system clipboard.
866    ///
867    /// This function attempts to access the generated password stored in the `self.generated_password`
868    /// field and copies it to the system clipboard using the `arboard` crate. If the operation succeeds,
869    /// a success message is set in the `self.error` field. In the event of an error while accessing the
870    /// clipboard or copying the text, the function returns a corresponding error.
871    ///
872    /// # Returns
873    ///
874    /// * `Ok(())` - If the password is successfully copied to the clipboard or no password was generated.
875    /// * `Err(anyhow::Error)` - If accessing the clipboard or copying the password fails.
876    ///
877    /// # Errors
878    ///
879    /// This function may return an error in the following scenarios:
880    /// * Failure to access or initialize the system clipboard.
881    /// * Failure to copy the generated password to the clipboard.
882    ///
883    /// # Side Effects
884    ///
885    /// * If a password is successfully copied to the clipboard, the `self.error` field is set with a success message.
886    ///
887    /// # Dependencies
888    ///
889    /// This function makes use of the `arboard` crate for clipboard access and text manipulation.
890    pub fn copy_generated_password(&mut self) -> Result<()> {
891        if let Some(password) = &self.generated_password {
892            let mut clipboard = arboard::Clipboard::new().map_err(|e| eyre!("Failed to access clipboard: {}", e))?;
893            clipboard
894                .set_text(password)
895                .map_err(|e| eyre!("Failed to copy to clipboard: {}", e))?;
896            self.error = Some("Password copied to clipboard".into());
897        }
898        Ok(())
899    }
900
901    pub fn use_generated_password(&mut self) {
902        if let Some(password) = &self.generated_password {
903            self.add_value = password.clone();
904            self.screen = Screen::AddItem;
905            self.add_focus = AddItemField::Name;
906        }
907    }
908
909    // Import/Export methods
910    pub fn open_import_export(&mut self, mode: ImportExportMode) {
911        self.ie_mode = mode;
912        self.ie_focus = ImportExportField::Path;
913        self.ie_path.clear();
914        self.ie_format_idx = 0;
915        self.error = None;
916        self.screen = Screen::ImportExport;
917    }
918
919    /// Executes the import or export operation based on the current application state.
920    ///
921    /// This method performs the following operations:
922    /// 1. Validates the provided file path and ensures it is not empty.
923    /// 2. Normalizes the file path to handle different path separators and expand the home directory.
924    /// 3. Determines the format of import/export (CSV, JSON, or backup).
925    /// 4. Executes the import or export operation based on the selected mode (`ImportExportMode`).
926    ///
927    /// ### Export Mode
928    /// - Creates necessary parent directories if they do not exist.
929    /// - Exports the current items to the specified file path in the selected format.
930    /// - Sets an appropriate success message indicating the number of items exported and the file path.
931    ///
932    /// ### Import Mode
933    /// - Ensures the specified file exists before proceeding.
934    /// - Imports items from the file in the specified format.
935    /// - Avoids importing duplicate items by checking against existing item names.
936    /// - Records the number of imported and skipped items due to duplication or errors.
937    /// - Updates the item list after a successful import and presents an appropriate summary message.
938    ///
939    /// ### Errors
940    /// - If the file path is empty, a user-friendly error message is set and the operation is aborted.
941    /// - If a directory creation fails during export, an error is returned.
942    /// - If certain items cannot be imported due to conflicts or other errors, they are counted as skipped.
943    ///
944    /// ### Remarks
945    /// - Upon completion (successful or not), the application state is updated to the main screen.
946    ///
947    /// ### Returns
948    /// - `Ok(())` if the operation completes successfully (even if some items were skipped).
949    /// - `Err` if a file path normalization or file operation fails during the execution.
950    ///
951    /// ### Preconditions
952    /// - `self.ie_path` must be set to a valid file path.
953    /// - The `self.ie_formats` array must include supported formats ("csv", "backup", "json").
954    /// - The `self.items` list is expected to contain the current application items for export/import validation.
955    ///
956    /// ### Postconditions
957    /// - Updates `self.error` with a descriptive message about the operation result.
958    /// - Changes the application state screen to `Screen::Main`.
959    pub fn execute_import_export(&mut self) -> Result<()> {
960        if self.ie_path.trim().is_empty() {
961            self.error = Some("Please enter a file path".into());
962            return Ok(());
963        }
964
965        // Normalize the path to handle different separators and expand the home directory
966        let normalized_path = app::App::normalize_path(&self.ie_path)?;
967        let path = PathBuf::from(normalized_path);
968
969        let format = match self.ie_formats[self.ie_format_idx] {
970            "csv" => ExportFormat::Csv,
971            "backup" => ExportFormat::ChamberBackup,
972            _ => ExportFormat::Json,
973        };
974
975        match self.ie_mode {
976            ImportExportMode::Export => {
977                // Create parent directories if they don't exist
978                if let Some(parent) = path.parent() {
979                    if !parent.exists() {
980                        std::fs::create_dir_all(parent)
981                            .map_err(|e| eyre!("Failed to create directory {}: {}", parent.display(), e))?;
982                    }
983                }
984
985                export_items(&self.items, &format, &path)?;
986                self.error = Some(format!("Exported {} items to {}", self.items.len(), path.display()));
987            }
988            ImportExportMode::Import => {
989                if !path.exists() {
990                    self.error = Some(format!("File does not exist: {}", path.display()));
991                    return Ok(());
992                }
993
994                let new_items = import_items(&path, &format)?;
995                if new_items.is_empty() {
996                    self.error = Some("No items found in file".into());
997                    return Ok(());
998                }
999
1000                let existing_names: std::collections::HashSet<String> =
1001                    self.items.iter().map(|item| item.name.clone()).collect();
1002
1003                let mut imported_count = 0;
1004                let mut skipped_count = 0;
1005
1006                for item in new_items {
1007                    if existing_names.contains(&item.name) {
1008                        skipped_count += 1;
1009                        continue;
1010                    }
1011
1012                    match self.vault.create_item(&item) {
1013                        Ok(()) => imported_count += 1,
1014                        Err(_) => skipped_count += 1,
1015                    }
1016                }
1017
1018                self.refresh_items()?;
1019                self.error = Some(format!("Imported {imported_count} items, skipped {skipped_count}"));
1020            }
1021        }
1022
1023        self.screen = Screen::Main;
1024        Ok(())
1025    }
1026
1027    // Helper method to normalize file paths
1028    fn normalize_path(input_path: &str) -> Result<String> {
1029        let path_str = input_path.trim();
1030
1031        // Handle home directory expansion
1032        let expanded_path = if path_str.strip_prefix('~').is_some() {
1033            if let Some(home_dir) = dirs::home_dir() {
1034                let rest = &path_str[1..];
1035                let rest = if rest.starts_with('/') || rest.starts_with('\\') {
1036                    &rest[1..]
1037                } else {
1038                    rest
1039                };
1040                home_dir.join(rest).to_string_lossy().to_string()
1041            } else {
1042                return Err(eyre!("Unable to determine home directory"));
1043            }
1044        } else {
1045            path_str.to_string()
1046        };
1047
1048        // Convert forward slashes to native path separators on Windows
1049        #[cfg(windows)]
1050        let normalized = expanded_path.replace('/', "\\");
1051
1052        #[cfg(not(windows))]
1053        let normalized = expanded_path;
1054
1055        Ok(normalized)
1056    }
1057
1058    pub fn set_status(&mut self, message: String, status_type: StatusType) {
1059        self.status_message = Some(message);
1060        self.status_type = status_type;
1061    }
1062
1063    pub fn clear_status(&mut self) {
1064        self.status_message = None;
1065    }
1066
1067    pub fn is_in_input_mode(&self) -> bool {
1068        match self.screen {
1069            Screen::AddItem
1070            | Screen::EditItem
1071            | Screen::ChangeMaster
1072            | Screen::GeneratePassword
1073            | Screen::ImportExport
1074            | Screen::Unlock => true,
1075            Screen::Main if !self.search_query.is_empty() => true, // Search mode
1076            _ => false,
1077        }
1078    }
1079
1080    /// Pastes content from the clipboard to the add item value field.
1081    ///
1082    /// This function retrieves text content from the system clipboard and appends it to
1083    /// the current `add_value` field. If the clipboard contains text, it will be added
1084    /// to the existing value. If accessing the clipboard fails, an appropriate status
1085    /// message is displayed.
1086    ///
1087    /// # Returns
1088    ///
1089    /// * `Ok(())` - If the paste operation completes successfully or if there's no text in clipboard.
1090    /// * `Err(anyhow::Error)` - If accessing the clipboard fails.
1091    ///
1092    /// # Errors
1093    ///
1094    /// - Returns an error if accessing the clipboard fails.
1095    /// - Sets a warning status if the clipboard is empty or contains no text.
1096    ///
1097    /// # Behavior
1098    ///
1099    /// - Retrieves text from the system clipboard using `arboard::Clipboard`.
1100    /// - Appends the clipboard content to the current `add_value` field.
1101    /// - Sets a success status message indicating the paste operation completed.
1102    /// - If clipboard is empty or contains no text, shows a warning message.
1103    pub fn paste_to_add_value(&mut self) -> Result<()> {
1104        if let Ok(mut clipboard) = arboard::Clipboard::new() {
1105            if let Ok(text) = clipboard.get_text() {
1106                // Clear existing content and insert new text
1107                self.add_value_textarea.select_all();
1108                self.add_value_textarea.cut();
1109                self.add_value_textarea.insert_str(text);
1110                self.set_status("Pasted from clipboard".to_string(), StatusType::Success);
1111                Ok(())
1112            } else {
1113                self.set_status("No text in clipboard".to_string(), StatusType::Warning);
1114                Ok(())
1115            }
1116        } else {
1117            self.set_status("Failed to access clipboard".to_string(), StatusType::Error);
1118            Ok(())
1119        }
1120    }
1121
1122    pub fn open_vault_selector(&mut self) {
1123        self.vault_selector.load_vaults(&self.vault_manager);
1124        self.vault_selector.show();
1125        self.screen = Screen::VaultSelector;
1126    }
1127
1128    /// Handles various actions related to vault management.
1129    ///
1130    /// This function processes the provided `VaultAction` and executes the corresponding logic
1131    /// to manage vaults, such as creating, updating, deleting, importing, and more.
1132    ///
1133    /// # Arguments
1134    ///
1135    /// * `action` - A `VaultAction` enum that specifies the action to be performed. Each variant
1136    ///   of `VaultAction` corresponds to a specific vault-related operation.
1137    ///
1138    /// # Returns
1139    ///
1140    /// * `Result<()>` - Returns `Ok(())` if the action was processed successfully; otherwise,
1141    ///   an error is returned if any operation fails.
1142    ///
1143    /// # `VaultAction` Variants
1144    ///
1145    /// - `VaultAction::Switch(vault_id)`
1146    ///   Switches to the specified vault by its ID.
1147    ///
1148    /// - `VaultAction::Create { name, description, category }`
1149    ///   Creates a new vault with the provided name, description, and category.
1150    ///
1151    /// - `VaultAction::Update { vault_id, name, description, category, favorite }`
1152    ///   Updates an existing vault with the given parameters, including optional fields like the
1153    ///   vault name, description, category, and favorite status.
1154    ///
1155    /// - `VaultAction::Delete { vault_id, delete_file }`
1156    ///   Deletes a vault specified by its ID. If `delete_file` is true, related files are also deleted.
1157    ///
1158    /// - `VaultAction::Import { path }`
1159    ///   Imports a vault from a specified file path.
1160    ///
1161    /// - `VaultAction::Refresh`
1162    ///   Reloads the list of available vaults and updates the UI to reflect any changes. Sets
1163    ///   a success status message indicating the vaults have been refreshed.
1164    ///
1165    /// - `VaultAction::Close`
1166    ///   Hides the vault selector and switches back to the main screen.
1167    ///
1168    /// - `VaultAction::ShowHelp`
1169    ///   Displays help information. (This action may be handled or ignored based on needs.)
1170    ///
1171    /// # Errors
1172    ///
1173    /// Returns an error if:
1174    /// - Switching to a vault fails.
1175    /// - The requested vault action encounters an issue (e.g., file access issues during imports or
1176    ///   failures in vault creation/deletion).
1177    pub fn handle_vault_action(&mut self, action: VaultAction) -> Result<()> {
1178        match action {
1179            VaultAction::Switch(vault_id) => {
1180                self.switch_to_vault(&vault_id)?;
1181            }
1182            VaultAction::Create {
1183                name,
1184                description,
1185                category,
1186            } => {
1187                self.create_vault(&name, description, &category);
1188            }
1189            VaultAction::Update {
1190                vault_id,
1191                name,
1192                description,
1193                category,
1194                favorite,
1195            } => {
1196                self.update_vault(vault_id, name, description, category, favorite);
1197            }
1198            VaultAction::Delete { vault_id, delete_file } => {
1199                self.delete_vault(&vault_id, delete_file);
1200            }
1201            VaultAction::Import { path } => {
1202                self.import_vault(&path);
1203            }
1204            VaultAction::Refresh => {
1205                self.vault_selector.load_vaults(&self.vault_manager);
1206                self.set_status("Vaults refreshed".to_string(), StatusType::Success);
1207            }
1208            VaultAction::Close => {
1209                self.vault_selector.hide();
1210                self.screen = Screen::Main;
1211            }
1212            VaultAction::ShowHelp => {
1213                // You can handle help display here or ignore it
1214            }
1215        }
1216        Ok(())
1217    }
1218
1219    fn switch_to_vault(&mut self, vault_id: &str) -> Result<String> {
1220        // First, switch the active vault in the registry
1221        self.vault_manager.switch_active_vault(vault_id)?;
1222
1223        // Try to open the vault with the current master password
1224        match self.vault_manager.open_vault(vault_id, &self.master_input) {
1225            Ok(()) => {
1226                // Successfully opened vault in the manager
1227                // Now we need to create our own unlocked instance
1228                let vault_info = self
1229                    .vault_manager
1230                    .registry
1231                    .get_vault(vault_id)
1232                    .ok_or_else(|| eyre!("Vault with id {} not found", vault_id))?;
1233                let mut new_vault = chamber_vault::Vault::open_or_create(Some(&vault_info.path))?;
1234                new_vault.unlock(&self.master_input)?;
1235
1236                // Replace our vault with the newly unlocked one
1237                self.vault = new_vault;
1238                self.refresh_items()?;
1239                self.set_status(format!("Switched to vault: {vault_id}"), StatusType::Success);
1240            }
1241            Err(_) => {
1242                // This vault has a different master password
1243                self.vault_selector.error_message = Some(format!(
1244                    "Vault '{vault_id}' was created with a different master password. \
1245                Please delete this vault and create a new one, or use the master password change feature to migrate it."
1246                ));
1247            }
1248        }
1249
1250        Ok(String::from(vault_id))
1251    }
1252
1253    fn create_vault(&mut self, name: &str, description: Option<String>, category: &str) {
1254        // Parse category
1255        let vault_category = match category.to_lowercase().as_str() {
1256            "personal" => chamber_vault::VaultCategory::Personal,
1257            "work" => chamber_vault::VaultCategory::Work,
1258            "team" => chamber_vault::VaultCategory::Team,
1259            "project" => chamber_vault::VaultCategory::Project,
1260            "testing" => chamber_vault::VaultCategory::Testing,
1261            "archive" => chamber_vault::VaultCategory::Archive,
1262            custom => chamber_vault::VaultCategory::Custom(custom.to_string()),
1263        };
1264
1265        // In a real implementation, you'd prompt for a password
1266        let password = &self.master_input;
1267
1268        // Validate that we have a password
1269        if password.is_empty() {
1270            self.vault_selector.error_message = Some("Master password is required to create vault".to_string());
1271        }
1272
1273        match self
1274            .vault_manager
1275            .create_vault(name.to_string(), None, vault_category, description, password)
1276        {
1277            Ok(_vault_id) => {
1278                self.vault_selector.load_vaults(&self.vault_manager);
1279                self.vault_selector.mode = VaultSelectorMode::Select;
1280                self.set_status(format!("Created vault: {name}"), StatusType::Success);
1281            }
1282            Err(e) => {
1283                self.vault_selector.error_message = Some(format!("Failed to create vault: {e}"));
1284            }
1285        }
1286    }
1287
1288    fn update_vault(
1289        &mut self,
1290        vault_id: String,
1291        name: Option<String>,
1292        description: Option<String>,
1293        category: Option<String>,
1294        favorite: Option<bool>,
1295    ) {
1296        let vault_category = category.map(|cat_str| match cat_str.to_lowercase().as_str() {
1297            "personal" => chamber_vault::VaultCategory::Personal,
1298            "work" => chamber_vault::VaultCategory::Work,
1299            "team" => chamber_vault::VaultCategory::Team,
1300            "project" => chamber_vault::VaultCategory::Project,
1301            "testing" => chamber_vault::VaultCategory::Testing,
1302            "archive" => chamber_vault::VaultCategory::Archive,
1303            custom => chamber_vault::VaultCategory::Custom(custom.to_string()),
1304        });
1305
1306        match self
1307            .vault_manager
1308            .update_vault_info(&vault_id, name.clone(), description, vault_category, favorite)
1309        {
1310            Ok(()) => {
1311                self.vault_selector.load_vaults(&self.vault_manager);
1312                self.vault_selector.mode = VaultSelectorMode::Select;
1313                self.set_status(
1314                    format!("Updated vault: {}", name.unwrap_or(vault_id)),
1315                    StatusType::Success,
1316                );
1317            }
1318            Err(e) => {
1319                self.vault_selector.error_message = Some(format!("Failed to update vault: {e}"));
1320            }
1321        }
1322    }
1323
1324    fn delete_vault(&mut self, vault_id: &str, delete_file: bool) {
1325        let is_active_vault = self.vault_manager.registry.active_vault_id.as_ref() == Some(&vault_id.to_string());
1326        let vault_count = self.vault_manager.registry.vaults.len();
1327
1328        // Prevent deletion of an active vault unless it's the only one
1329        if is_active_vault && vault_count > 1 {
1330            self.set_status(
1331                "Cannot delete active vault. Switch to another vault first.".to_string(),
1332                StatusType::Error,
1333            );
1334            return;
1335        }
1336
1337        match self.vault_manager.delete_vault(vault_id, delete_file) {
1338            Ok(()) => {
1339                self.vault_selector.load_vaults(&self.vault_manager);
1340                self.vault_selector.mode = VaultSelectorMode::Select;
1341                self.set_status(format!("Deleted vault: {vault_id}"), StatusType::Success);
1342            }
1343            Err(e) => {
1344                self.set_status(format!("Failed to delete vault: {e}"), StatusType::Error);
1345            }
1346        }
1347    }
1348
1349    fn import_vault(&mut self, path: &str) {
1350        let path_buf = std::path::PathBuf::from(&path);
1351        let name = path_buf
1352            .file_stem()
1353            .and_then(|s| s.to_str())
1354            .unwrap_or("Imported Vault")
1355            .to_string();
1356
1357        match self
1358            .vault_manager
1359            .import_vault(&path_buf, name.clone(), chamber_vault::VaultCategory::Personal, true)
1360        {
1361            Ok(_vault_id) => {
1362                self.vault_selector.load_vaults(&self.vault_manager);
1363                self.vault_selector.mode = VaultSelectorMode::Select;
1364                self.set_status(format!("Imported vault: {name}"), StatusType::Success);
1365            }
1366            Err(e) => {
1367                self.vault_selector.error_message = Some(format!("Failed to import vault: {e}"));
1368            }
1369        }
1370    }
1371    pub async fn update_activity(&mut self) {
1372        if let Some(service) = &self.auto_lock_service {
1373            service.update_activity().await;
1374        }
1375    }
1376
1377    pub async fn check_auto_lock(&mut self) -> bool {
1378        if let Some(service) = &self.auto_lock_service {
1379            if service.activity_tracker.should_auto_lock().await {
1380                self.auto_locked = true;
1381                self.screen = Screen::Unlock;
1382                return true;
1383            }
1384        }
1385        false
1386    }
1387
1388    pub async fn get_time_until_auto_lock(&self) -> Option<chrono::Duration> {
1389        if let Some(service) = &self.auto_lock_service {
1390            service.get_time_until_lock().await
1391        } else {
1392            None
1393        }
1394    }
1395
1396    pub async fn update_countdown_info(&mut self) {
1397        if let Some(time_left) = self.get_time_until_auto_lock().await {
1398            self.countdown_info = Some(CountdownInfo {
1399                enabled: true,
1400                minutes_left: time_left.num_minutes(),
1401                seconds_left: time_left.num_seconds(),
1402            });
1403        } else {
1404            self.countdown_info = None;
1405        }
1406    }
1407
1408    // Synchronous method for the drawing function
1409    pub const fn get_countdown_info(&self) -> Option<&CountdownInfo> {
1410        self.countdown_info.as_ref()
1411    }
1412}