import Foundation import Observation enum Tab: String, CaseIterable, Identifiable { case eis = "EIS" case lsv = "LSV" case amp = "Amperometry" case chlorine = "Chlorine" case ph = "pH" case sessions = "Sessions" var id: String { rawValue } } // MARK: - App State @Observable final class AppState { var tab: Tab = .eis var status: String = "Disconnected" var bleConnected: Bool = false var tempC: Float = 25.0 // EIS var eisPoints: [EisPoint] = [] var sweepTotal: UInt16 = 0 var freqStart: String = "1000" var freqStop: String = "200000" var ppd: String = "10" var rtia: Rtia = .r5K var rcal: Rcal = .r3K var electrode: Electrode = .fourWire // LSV var lsvPoints: [LsvPoint] = [] var lsvTotal: UInt16 = 0 var lsvStartV: String = "0" var lsvStopV: String = "500" var lsvScanRate: String = "50" var lsvRtia: LpRtia = .r10K // Amperometry var ampPoints: [AmpPoint] = [] var ampTotal: UInt16 = 0 var ampRunning: Bool = false var ampVHold: String = "200" var ampInterval: String = "100" var ampDuration: String = "60" var ampRtia: LpRtia = .r10K // Chlorine var clPoints: [ClPoint] = [] var clResult: ClResult? = nil var clTotal: UInt16 = 0 var clCondV: String = "800" var clCondT: String = "2000" var clFreeV: String = "100" var clTotalV: String = "-200" var clDepT: String = "5000" var clMeasT: String = "5000" var clRtia: LpRtia = .r10K // pH var phResult: PhResult? = nil var phStabilize: String = "30" // Reference baselines var eisRef: [EisPoint]? = nil var lsvRef: [LsvPoint]? = nil var ampRef: [AmpPoint]? = nil var clRef: (points: [ClPoint], result: ClResult)? = nil var phRef: PhResult? = nil // Device reference collection var collectingRefs: Bool = false var hasDeviceRefs: Bool = false // Clean var cleanV: String = "1200" var cleanDur: String = "30" // MARK: - Actions func applyEISSettings() { let fs = Float(freqStart) ?? 1000 let fe = Float(freqStop) ?? 200000 let p = UInt16(ppd) ?? 10 status = "Applying: \(fs)-\(fe) Hz, \(p) PPD" } func startSweep() { eisPoints.removeAll() status = "Starting sweep..." } func startLSV() { lsvPoints.removeAll() let vs = Float(lsvStartV) ?? 0 let ve = Float(lsvStopV) ?? 500 status = "Starting LSV: \(vs)-\(ve) mV" } func startAmp() { ampPoints.removeAll() ampRunning = true status = "Starting amperometry..." } func stopAmp() { ampRunning = false status = "Stopping amperometry..." } func startChlorine() { clPoints.removeAll() clResult = nil status = "Starting chlorine measurement..." } func startPh() { phResult = nil status = "Starting pH measurement..." } func setReference() { switch tab { case .eis where !eisPoints.isEmpty: eisRef = eisPoints status = "EIS reference set (\(eisPoints.count) pts)" case .lsv where !lsvPoints.isEmpty: lsvRef = lsvPoints status = "LSV reference set (\(lsvPoints.count) pts)" case .amp where !ampPoints.isEmpty: ampRef = ampPoints status = "Amp reference set (\(ampPoints.count) pts)" case .chlorine where !clPoints.isEmpty: if let r = clResult { clRef = (clPoints, r) status = "Chlorine reference set" } case .ph: if let r = phResult { phRef = r status = String(format: "pH reference set (%.2f)", r.ph) } default: break } } func clearReference() { switch tab { case .eis: eisRef = nil; status = "EIS reference cleared" case .lsv: lsvRef = nil; status = "LSV reference cleared" case .amp: ampRef = nil; status = "Amp reference cleared" case .chlorine: clRef = nil; status = "Chlorine reference cleared" case .ph: phRef = nil; status = "pH reference cleared" case .sessions: break } } func collectRefs() { collectingRefs = true status = "Starting reference collection..." } func clearRefs() { collectingRefs = false hasDeviceRefs = false eisRef = nil lsvRef = nil ampRef = nil clRef = nil phRef = nil status = "Refs cleared" } func startClean() { let v = Float(cleanV) ?? 1200 let d = Float(cleanDur) ?? 30 status = String(format: "Cleaning: %.0f mV for %.0fs", v, d) } var hasCurrentRef: Bool { switch tab { case .eis: eisRef != nil case .lsv: lsvRef != nil case .amp: ampRef != nil case .chlorine: clRef != nil case .ph: phRef != nil case .sessions: false } } var hasCurrentData: Bool { switch tab { case .eis: !eisPoints.isEmpty case .lsv: !lsvPoints.isEmpty case .amp: !ampPoints.isEmpty case .chlorine: clResult != nil case .ph: phResult != nil case .sessions: false } } // MARK: - Measurement loading func loadMeasurement(_ measurement: Measurement) { guard let id = measurement.id, let type = MeasurementType(rawValue: measurement.type) else { return } do { switch type { case .eis: eisPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: EisPoint.self) tab = .eis status = "Loaded EIS (\(eisPoints.count) pts)" case .lsv: lsvPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: LsvPoint.self) tab = .lsv status = "Loaded LSV (\(lsvPoints.count) pts)" case .amp: ampPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: AmpPoint.self) tab = .amp status = "Loaded Amp (\(ampPoints.count) pts)" case .chlorine: clPoints = try Storage.shared.fetchTypedPoints(measurementId: id, as: ClPoint.self) if let summary = measurement.resultSummary { clResult = try JSONDecoder().decode(ClResult.self, from: summary) } tab = .chlorine status = "Loaded Chlorine (\(clPoints.count) pts)" case .ph: if let summary = measurement.resultSummary { phResult = try JSONDecoder().decode(PhResult.self, from: summary) } tab = .ph status = "Loaded pH result" } } catch { status = "Load failed: \(error.localizedDescription)" } } func loadAsReference(_ measurement: Measurement) { guard let id = measurement.id, let type = MeasurementType(rawValue: measurement.type) else { return } do { switch type { case .eis: eisRef = try Storage.shared.fetchTypedPoints(measurementId: id, as: EisPoint.self) status = "EIS reference loaded (\(eisRef?.count ?? 0) pts)" case .lsv: lsvRef = try Storage.shared.fetchTypedPoints(measurementId: id, as: LsvPoint.self) status = "LSV reference loaded (\(lsvRef?.count ?? 0) pts)" case .amp: ampRef = try Storage.shared.fetchTypedPoints(measurementId: id, as: AmpPoint.self) status = "Amp reference loaded (\(ampRef?.count ?? 0) pts)" case .chlorine: let pts = try Storage.shared.fetchTypedPoints(measurementId: id, as: ClPoint.self) if let summary = measurement.resultSummary { let result = try JSONDecoder().decode(ClResult.self, from: summary) clRef = (pts, result) status = "Chlorine reference loaded" } case .ph: if let summary = measurement.resultSummary { phRef = try JSONDecoder().decode(PhResult.self, from: summary) status = String(format: "pH reference loaded (%.2f)", phRef?.ph ?? 0) } } } catch { status = "Reference load failed: \(error.localizedDescription)" } } }