import Foundation import SwiftUI @Observable final class AuthViewModel { var email = "" var password = "" var confirmPassword = "" var username = "" var confirmationCode = "" var isLoading = false var errorMessage: String? var showConfirmation = false var registrationMessage: String? var serverHost = Constants.defaultHost var serverPort = String(Constants.defaultPort) var hasSavedCredentials = false var isBiometricLoading = false enum AuthMode { case login, register, pairing } var mode: AuthMode = .login func checkSavedCredentials() { hasSavedCredentials = KeychainService.hasSavedCredentials() && KeychainService.isBiometricAvailable() } func login(appState: AppState) async { guard !email.isEmpty, !password.isEmpty else { errorMessage = "Email and password are required" return } isLoading = true errorMessage = nil // Only connect if not already connected if await !appState.chatClient.isConnected { do { let port = UInt16(serverPort) ?? Constants.defaultPort try await appState.chatClient.connect(host: serverHost, port: port) } catch { isLoading = false errorMessage = "Connection failed: \(error.localizedDescription)" return } } let (success, message) = await appState.chatClient.login(email: email, password: password) isLoading = false if success { appState.email = email appState.isLoggedIn = true appState.connectionStatus = .connected appState.startConnectionMonitor() if let userId = await appState.chatClient.userId { appState.currentUser = User(id: userId, username: await appState.chatClient.username, email: email) } // Save credentials for biometric login next time if KeychainService.isBiometricAvailable() { let port = UInt16(serverPort) ?? Constants.defaultPort try? KeychainService.saveCredentials( email: email, password: password, host: serverHost, port: port ) } // Clear password from memory after successful login password = "" confirmPassword = "" } else { errorMessage = message } } func biometricLogin(appState: AppState) async { isBiometricLoading = true errorMessage = nil do { let creds = try KeychainService.loadCredentials() email = creds.email password = creds.password serverHost = creds.host serverPort = String(creds.port) isBiometricLoading = false await login(appState: appState) // If login failed, reset to defaults so the form isn't stuck on stale values if !appState.isLoggedIn { serverHost = Constants.defaultHost serverPort = String(Constants.defaultPort) password = "" KeychainService.deleteCredentials() hasSavedCredentials = false } } catch KeychainService.KeychainError.biometricFailed { isBiometricLoading = false // User cancelled — just let them type manually } catch { isBiometricLoading = false errorMessage = error.localizedDescription } } func register(appState: AppState) async { guard !email.isEmpty, !password.isEmpty, !username.isEmpty else { errorMessage = "All fields are required" return } guard password == confirmPassword else { errorMessage = "Passwords don't match" return } isLoading = true errorMessage = nil #if DEBUG print("DEBUG register: connecting to \(serverHost):\(serverPort)") #endif if await !appState.chatClient.isConnected { do { let port = UInt16(serverPort) ?? Constants.defaultPort try await appState.chatClient.connect(host: serverHost, port: port) #if DEBUG print("DEBUG register: connected successfully") #endif } catch { isLoading = false #if DEBUG print("DEBUG register: connection failed - \(error)") #endif errorMessage = "Connection failed: \(error.localizedDescription)" return } } #if DEBUG print("DEBUG register: calling chatClient.register") #endif let (success, message) = await appState.chatClient.register(username: username, password: password, email: email) isLoading = false #if DEBUG print("DEBUG AuthViewModel: register returned success=\(success), message=\(message)") #endif if success { registrationMessage = message showConfirmation = true #if DEBUG print("DEBUG AuthViewModel: showConfirmation set to true") #endif } else { errorMessage = message #if DEBUG print("DEBUG AuthViewModel: errorMessage set to \(message)") #endif } } func confirmRegistration(appState: AppState) async { guard !confirmationCode.isEmpty else { errorMessage = "Enter the confirmation code" return } isLoading = true errorMessage = nil let (success, message) = await appState.chatClient.confirmRegistration( email: email, username: username, code: confirmationCode ) isLoading = false if success { registrationMessage = message // Auto-login after registration await login(appState: appState) } else { errorMessage = message } } }