// src/pages/register/state.rs use canvas::{DataProvider, AppMode, FormEditor}; use std::fmt; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use canvas::keymap::KeyEventOutcome; use crate::pages::register::suggestions::role_suggestions_sync; /// Represents the state of the Registration form UI #[derive(Debug, Clone)] pub struct RegisterState { pub username: String, pub email: String, pub password: String, pub password_confirmation: String, pub role: String, pub error_message: Option, pub current_field: usize, pub current_cursor_pos: usize, pub has_unsaved_changes: bool, pub app_mode: canvas::AppMode, } impl Default for RegisterState { fn default() -> Self { Self { username: String::new(), email: String::new(), password: String::new(), password_confirmation: String::new(), role: String::new(), error_message: None, current_field: 0, current_cursor_pos: 0, has_unsaved_changes: false, app_mode: canvas::AppMode::Edit, } } } impl RegisterState { pub fn new() -> Self { Self { app_mode: canvas::AppMode::Edit, ..Default::default() } } pub fn current_field(&self) -> usize { self.current_field } pub fn current_cursor_pos(&self) -> usize { self.current_cursor_pos } pub fn set_current_field(&mut self, index: usize) { if index < 5 { self.current_field = index; } } pub fn set_current_cursor_pos(&mut self, pos: usize) { self.current_cursor_pos = pos; } pub fn get_current_input(&self) -> &str { match self.current_field { 0 => &self.username, 1 => &self.email, 2 => &self.password, 3 => &self.password_confirmation, 4 => &self.role, _ => "", } } pub fn get_current_input_mut(&mut self, index: usize) -> &mut String { match index { 0 => &mut self.username, 1 => &mut self.email, 2 => &mut self.password, 3 => &mut self.password_confirmation, 4 => &mut self.role, _ => panic!("Invalid current_field index in RegisterState"), } } pub fn current_mode(&self) -> AppMode { self.app_mode } pub fn has_unsaved_changes(&self) -> bool { self.has_unsaved_changes } pub fn set_has_unsaved_changes(&mut self, changed: bool) { self.has_unsaved_changes = changed; } } impl DataProvider for RegisterState { fn field_count(&self) -> usize { 5 } fn field_name(&self, index: usize) -> &str { match index { 0 => "Username", 1 => "Email (Optional)", 2 => "Password (Optional)", 3 => "Confirm Password", 4 => "Role (Optional)", _ => "", } } fn field_value(&self, index: usize) -> &str { match index { 0 => &self.username, 1 => &self.email, 2 => &self.password, 3 => &self.password_confirmation, 4 => &self.role, _ => "", } } fn set_field_value(&mut self, index: usize, value: String) { match index { 0 => self.username = value, 1 => self.email = value, 2 => self.password = value, 3 => self.password_confirmation = value, 4 => self.role = value, _ => {} } self.has_unsaved_changes = true; } fn supports_suggestions(&self, field_index: usize) -> bool { field_index == 4 // only Role field supports suggestions } } /// Wrapper that owns both the raw register state and its editor pub struct RegisterFormState { pub state: RegisterState, pub editor: FormEditor, pub focus_outside_canvas: bool, pub focused_button_index: usize, } impl Default for RegisterFormState { fn default() -> Self { Self::new() } } // manual Debug because FormEditor doesn’t implement Debug impl fmt::Debug for RegisterFormState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RegisterFormState") .field("state", &self.state) .finish() } } impl RegisterFormState { /// Sync the editor's data provider back into our state pub fn sync_from_editor(&mut self) { // The FormEditor holds the authoritative data let dp = self.editor.data_provider(); self.state = dp.clone(); // because RegisterState: Clone } pub fn new() -> Self { let state = RegisterState::default(); let editor = FormEditor::new(state.clone()); Self { state, editor, focus_outside_canvas: false, focused_button_index: 0, } } // === Delegates to RegisterState === pub fn username(&self) -> &str { &self.state.username } pub fn username_mut(&mut self) -> &mut String { &mut self.state.username } pub fn email(&self) -> &str { &self.state.email } pub fn email_mut(&mut self) -> &mut String { &mut self.state.email } pub fn password(&self) -> &str { &self.state.password } pub fn password_mut(&mut self) -> &mut String { &mut self.state.password } pub fn password_confirmation(&self) -> &str { &self.state.password_confirmation } pub fn password_confirmation_mut(&mut self) -> &mut String { &mut self.state.password_confirmation } pub fn role(&self) -> &str { &self.state.role } pub fn role_mut(&mut self) -> &mut String { &mut self.state.role } pub fn error_message(&self) -> Option<&String> { self.state.error_message.as_ref() } pub fn set_error_message(&mut self, msg: Option) { self.state.error_message = msg; } pub fn has_unsaved_changes(&self) -> bool { self.state.has_unsaved_changes } pub fn set_has_unsaved_changes(&mut self, changed: bool) { self.state.has_unsaved_changes = changed; } pub fn clear(&mut self) { self.state.username.clear(); self.state.email.clear(); self.state.password.clear(); self.state.password_confirmation.clear(); self.state.role.clear(); self.state.error_message = None; self.state.has_unsaved_changes = false; self.state.current_field = 0; self.state.current_cursor_pos = 0; } // === Delegates to cursor/input === pub fn current_field(&self) -> usize { self.state.current_field() } pub fn set_current_field(&mut self, index: usize) { self.state.set_current_field(index); } pub fn current_cursor_pos(&self) -> usize { self.state.current_cursor_pos() } pub fn set_current_cursor_pos(&mut self, pos: usize) { self.state.set_current_cursor_pos(pos); } pub fn get_current_input(&self) -> &str { self.state.get_current_input() } pub fn get_current_input_mut(&mut self) -> &mut String { self.state.get_current_input_mut(self.state.current_field) } // === Delegates to FormEditor === pub fn mode(&self) -> canvas::AppMode { self.editor.mode() } pub fn cursor_position(&self) -> usize { self.editor.cursor_position() } pub fn handle_key_event( &mut self, key_event: crossterm::event::KeyEvent, ) -> canvas::keymap::KeyEventOutcome { // Only customize behavior for the Role field (index 4) in Edit mode let in_role_field = self.editor.current_field() == 4; let in_edit_mode = self.editor.mode() == canvas::AppMode::Edit; if in_role_field && in_edit_mode { match key_event.code { // Tab: open suggestions if inactive; otherwise cycle next KeyCode::Tab => { if !self.editor.is_suggestions_active() { if let Some(query) = self.editor.start_suggestions(4) { let items = role_suggestions_sync(&query); let applied = self.editor.apply_suggestions_result(4, &query, items); if applied { self.editor.update_inline_completion(); } } } else { // Cycle to next suggestion self.editor.suggestions_next(); } return KeyEventOutcome::Consumed(None); } // Shift+Tab (BackTab): cycle suggestions too (fallback to next) KeyCode::BackTab => { if self.editor.is_suggestions_active() { // If your canvas exposes suggestions_prev(), use it here. // Fallback: cycle next. self.editor.suggestions_next(); return KeyEventOutcome::Consumed(None); } } // Enter: if suggestions active — apply selected suggestion KeyCode::Enter => { if self.editor.is_suggestions_active() { let _ = self.editor.apply_suggestion(); return KeyEventOutcome::Consumed(None); } } // Esc: close suggestions if active KeyCode::Esc => { if self.editor.is_suggestions_active() { self.editor.close_suggestions(); return KeyEventOutcome::Consumed(None); } } // Character input: first let editor mutate text, then refilter if active KeyCode::Char(_) => { let outcome = self.editor.handle_key_event(key_event); if self.editor.is_suggestions_active() { if let Some(query) = self.editor.start_suggestions(4) { let items = role_suggestions_sync(&query); let applied = self.editor.apply_suggestions_result(4, &query, items); if applied { self.editor.update_inline_completion(); } } } return outcome; } // Backspace/Delete: mutate then refilter if active KeyCode::Backspace | KeyCode::Delete => { let outcome = self.editor.handle_key_event(key_event); if self.editor.is_suggestions_active() { if let Some(query) = self.editor.start_suggestions(4) { let items = role_suggestions_sync(&query); let applied = self.editor.apply_suggestions_result(4, &query, items); if applied { self.editor.update_inline_completion(); } } } return outcome; } _ => { /* fall through to default */ } } } // Default: let canvas handle it self.editor.handle_key_event(key_event) } }