import Foundation import Observation enum DebugSupervisionMode: String, CaseIterable, Identifiable { case none case forceSupervised case forceUnsupervised var id: String { rawValue } var title: String { switch self { case .none: return "Aus" case .forceSupervised: return "Force Supervised" case .forceUnsupervised: return "Force Unsupervised" } } } @MainActor @Observable final class WizardModel { var step: WizardStep = .macRegistration var device: DeviceState? var supervisionLog: [String] = [] var supervisionRunning: Bool = false var supervisionError: String? var enrollmentLog: [String] = [] var enrollmentRunning: Bool = false var enrollmentError: String? var configureLog: [String] = [] var configureRunning: Bool = false var configureError: String? var showAdvancedLogs: Bool = false var cooldownEndsAt: Date? // Debug-Reset State var supervisionMode: DebugSupervisionMode = .none var resetRunning: Bool = false var resetStatus: String? var resetAll: Bool = true var resetEnrollmentProfile: Bool = true var resetLockProfile: Bool = true var resetApp: Bool = true // Auth + Magic State var authSession: AuthSession? var showingLogin: Bool = false var showingHub: Bool = false var showingManageBindings: Bool = false var magicRegistration: MagicRegistration? var registrationError: String? init() { // Load existing session from keychain authSession = AuthService.shared.currentSession() showingLogin = (authSession == nil) // Nach Login direkt zum Hub statt Mac-Auto-Registrierung showingHub = (authSession != nil) } func advance() { if let next = WizardStep(rawValue: step.rawValue + 1) { step = next } } func goTo(_ s: WizardStep) { step = s } // MARK: - Mac Registration /// Registriert den aktuellen Mac im Backend. /// Wirft MagicError.limitReached falls Device-Limit erreicht. func registerMac() async throws { registrationError = nil do { let macInfo = try MacDeviceDetector.detect() let registration = try await MagicAPIClient.shared.register( deviceId: macInfo.deviceId, hostname: macInfo.hostname, model: macInfo.model, osVersion: macInfo.osVersion ) magicRegistration = registration } catch let error as MagicError { // Bei limit_reached → öffne ManageBindingsView if case .limitReached(_) = error { showingManageBindings = true } registrationError = error.localizedDescription throw error } catch { registrationError = error.localizedDescription throw error } } func handleLogin(session: AuthSession) { authSession = session showingLogin = false showingHub = true } func handleLogout() async { await AuthService.shared.signOut() authSession = nil showingLogin = true showingHub = false reset() } // MARK: - Hub Navigation /// User wählt 'iOS-Gerät hinzufügen' im Hub. func startIOSFlow() { showingHub = false step = .welcome } /// User wählt 'Mac schützen' im Hub. func startMacFlow() { showingHub = false step = .macRegistration } /// Zurück zur Geräte-Übersicht. func returnToHub() { reset() showingHub = true } func reset() { step = .macRegistration device = nil supervisionLog = [] enrollmentLog = [] configureLog = [] supervisionError = nil enrollmentError = nil configureError = nil showAdvancedLogs = false cooldownEndsAt = nil resetStatus = nil magicRegistration = nil registrationError = nil } func startDebugReset() { guard device != nil else { resetStatus = "Kein iPhone erkannt." return } resetRunning = true resetStatus = "Führe Debug-Reset aus …" Task { do { var changes: [String] = [] let removeEnrollment = resetAll || resetEnrollmentProfile let removeLock = resetAll || resetLockProfile let removeApp = resetAll || resetApp let installedProfileIDs = await DeviceDetector.installedProfileIDs() var profileIDs: [String] = [] if removeEnrollment, installedProfileIDs.contains(DeviceState.enrollmentProfileID) { profileIDs.append(DeviceState.enrollmentProfileID) } if removeLock, installedProfileIDs.contains(DeviceState.lockProfileID) { profileIDs.append(DeviceState.lockProfileID) } if !profileIDs.isEmpty { try await DeviceDetector.removeProfiles(identifiers: profileIDs) changes.append("Profile gelöscht: \(profileIDs.joined(separator: ", "))") } if removeApp { try await DeviceDetector.removeApp(bundleID: "org.rebreak.app") changes.append("App gelöscht: org.rebreak.app") } switch supervisionMode { case .forceSupervised: _ = try await SuperviseRunner.supervise(verbose: false) { _ in } changes.append("Mode gesetzt: supervised") case .forceUnsupervised: _ = try await SuperviseRunner.unsupervise { _ in } changes.append("Mode gesetzt: unsupervised") case .none: break } let nowInstalledProfiles = await DeviceDetector.installedProfileIDs() let nowApps = await DeviceDetector.installedAppBundleIDs() let status = await DeviceDetector.readSupervisionStatus() await MainActor.run { if changes.isEmpty { resetStatus = "Keine Aktion gewählt." } else { resetStatus = "✓ \(changes.joined(separator: " · "))" } if var device = self.device { device.installedProfileIDs = nowInstalledProfiles device.installedAppBundleIDs = nowApps device.isSupervised = status.isSupervised device.supervisorOrgName = status.organizationName device.isFmiOn = status.findMyEnabled device.isEnrolled = nowInstalledProfiles.contains(DeviceState.enrollmentProfileID) if !nowApps.contains("org.rebreak.app") { device.isManaged = false } if !nowInstalledProfiles.contains(DeviceState.lockProfileID) { device.isFilterActive = false } self.device = device } resetRunning = false } } catch { await MainActor.run { resetStatus = "✗ Reset fehlgeschlagen: \(error.localizedDescription)" resetRunning = false } } } } }