import Foundation
import Observation

enum ClAutoState: Equatable {
    case idle, lsvRunning, measureRunning
}

enum LsvDensityMode: String, CaseIterable, Identifiable {
    case ptsPerMv = "pts/mV"
    case ptsPerSec = "pts/s"
    var id: String { rawValue }
}

enum Tab: String, CaseIterable, Identifiable {
    case eis = "EIS"
    case lsv = "LSV"
    case amp = "Amperometry"
    case chlorine = "Chlorine"
    case ph = "pH"
    case calibrate = "Calibrate"
    case sessions = "Sessions"
    case connection = "Connection"

    var id: String { rawValue }
}

// MARK: - App State

@Observable
final class AppState {
    let transport: UDPManager
    var tab: Tab = .eis
    var status: String = "Disconnected"
    var tempC: Float = 25.0

    var connected: Bool { transport.state == .connected }

    // 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 lsvPeaks: [LsvPeak] = []
    var lsvTotal: UInt16 = 0
    var lsvStartV: String = "0"
    var lsvStopV: String = "500"
    var lsvScanRate: String = "50"
    var lsvRtia: LpRtia = .r10K
    var lsvDensityMode: LsvDensityMode = .ptsPerMv
    var lsvDensity: String = "1"

    // 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
    var clManualPeaks: Bool = false
    var clAutoState: ClAutoState = .idle
    var clAutoPotentials: ClPotentials? = nil

    // 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
    private var eisRefs: [Int: [EisPoint]] = [:]
    private var refMode: UInt8?
    private var refRtia: UInt8?

    // Session
    var currentSessionId: Int64? = nil
    var firmwareSessionMap: [UInt8: Int64] = [:]
    var sessionListReceived: Bool = false
    private var pendingEspTimestamp: Int64? = nil

    // Calibration
    var calVolumeGal: Double = 25
    var calNaclPpm: String = "2500"
    var calClPpm: String = "5"
    var calBleachPct: String = "7.825"
    var calTempC: String = "40"
    var calCellConstant: Double? = nil
    var calRs: Double? = nil
    var clFactor: Double? = nil
    var clCalKnownPpm: String = "5"
    var phSlope: Double? = nil
    var phOffset: Double? = nil
    var phCalPoints: [(ph: Double, mV: Double)] = []
    var phCalKnown: String = "7.00"

    // Clean
    var cleanV: String = "1200"
    var cleanDur: String = "30"

    init() {
        transport = UDPManager()
        transport.setMessageHandler { [weak self] msg in
            self?.handleMessage(msg)
        }
        transport.setDisconnectHandler { [weak self] in
            self?.sessionListReceived = false
            self?.firmwareSessionMap.removeAll()
        }
    }

    // MARK: - Send helper

    func send(_ sysex: [UInt8]) {
        transport.send(sysex)
    }

    // MARK: - Message handler

    private func handleMessage(_ msg: EisMessage) {
        switch msg {

        case .sweepStart(let numPoints, let freqStart, let freqStop, let espTs, _):
            pendingEspTimestamp = espTs.map { Int64($0) }
            if collectingRefs {
                eisPoints.removeAll()
                sweepTotal = numPoints
            } else {
                eisPoints.removeAll()
                sweepTotal = numPoints
                status = String(format: "Sweep: %d pts, %.0f--%.0f Hz", numPoints, freqStart, freqStop)
            }

        case .dataPoint(_, let point):
            eisPoints.append(point)
            if !collectingRefs {
                status = "Receiving: \(eisPoints.count)/\(sweepTotal)"
            }

        case .sweepEnd:
            if collectingRefs {
                if let r = refRtia, r < 8 {
                    eisRefs[Int(r)] = eisPoints
                }
                eisPoints.removeAll()
            } else {
                saveEis()
                status = "Sweep complete: \(eisPoints.count) points"
            }

        case .config(let cfg):
            freqStart = String(format: "%.0f", cfg.freqStart)
            freqStop = String(format: "%.0f", cfg.freqStop)
            ppd = "\(cfg.ppd)"
            rtia = cfg.rtia
            rcal = cfg.rcal
            electrode = cfg.electrode
            if !sessionListReceived {
                sessionListReceived = true
                send(buildSysexSessionList())
            }
            status = "Config received"

        case .lsvStart(let numPoints, let vStart, let vStop, let espTs, _):
            pendingEspTimestamp = espTs.map { Int64($0) }
            lsvPoints.removeAll()
            lsvTotal = numPoints
            status = String(format: "LSV: %d pts, %.0f--%.0f mV", numPoints, vStart, vStop)

        case .lsvPoint(_, let point):
            lsvPoints.append(point)
            status = "LSV: \(lsvPoints.count)/\(lsvTotal)"

        case .lsvEnd:
            saveLsv()
            lsvPeaks = detectLsvPeaks(lsvPoints)
            var st = "LSV complete: \(lsvPoints.count) points"
            if let s = phSlope, let o = phOffset, abs(s) > 1e-6 {
                if let peak = detectQhqPeak(lsvPoints) {
                    let ph = (Double(peak) - o) / s
                    st += String(format: "  |  pH=%.2f", ph)
                }
            }
            status = st

            if clAutoState == .lsvRunning {
                let pots = deriveClPotentials(lsvPoints)
                clFreeV = String(format: "%.0f", pots.vFree)
                clTotalV = String(format: "%.0f", pots.vTotal)
                clAutoPotentials = pots
                clAutoState = .measureRunning

                let vCond = Float(clCondV) ?? 800
                let tCond = Float(clCondT) ?? 2000
                let tDep = Float(clDepT) ?? 5000
                let tMeas = Float(clMeasT) ?? 5000
                send(buildSysexGetTemp())
                send(buildSysexStartCl(
                    vCond: vCond, tCondMs: tCond, vFree: pots.vFree, vTotal: pots.vTotal,
                    tDepMs: tDep, tMeasMs: tMeas, lpRtia: clRtia
                ))
                status = String(format: "Auto Cl: measuring (free=%.0f, total=%.0f)", pots.vFree, pots.vTotal)
            }

        case .ampStart(let vHold, let espTs, _):
            pendingEspTimestamp = espTs.map { Int64($0) }
            ampPoints.removeAll()
            ampRunning = true
            status = String(format: "Amp: %.0f mV", vHold)

        case .ampPoint(let index, let point):
            ampPoints.append(point)
            ampTotal = index + 1
            status = "Amp: \(ampPoints.count) pts"

        case .ampEnd:
            ampRunning = false
            saveAmp()
            status = "Amp complete: \(ampPoints.count) points"

        case .clStart(let numPoints, let espTs, _):
            pendingEspTimestamp = espTs.map { Int64($0) }
            clPoints.removeAll()
            clResult = nil
            clTotal = numPoints
            status = "Chlorine: \(numPoints) pts"

        case .clPoint(_, let point):
            clPoints.append(point)
            status = "Chlorine: \(clPoints.count)/\(clTotal)"

        case .clResult(let r):
            clResult = r
            status = String(format: "Chlorine: free=%.3f uA, total=%.3f uA", r.iFreeUa, r.iTotalUa)

        case .clEnd:
            saveCl()
            if clAutoState == .measureRunning {
                clAutoState = .idle
                if let pots = clAutoPotentials {
                    let fd = pots.vFreeDetected ? "" : " dflt"
                    let td = pots.vTotalDetected ? "" : " dflt"
                    status = String(format: "Auto Cl complete: %d pts (free=%.0f%@, total=%.0f%@)",
                                    clPoints.count, pots.vFree, fd, pots.vTotal, td)
                } else {
                    status = "Chlorine complete: \(clPoints.count) points"
                }
            } else {
                status = "Chlorine complete: \(clPoints.count) points"
            }

        case .phResult(let r):
            transport.measuring = false
            if collectingRefs {
                phRef = r
            } else {
                savePh(r)
                status = String(format: "pH: %.2f (OCP=%.1f mV, T=%.1fC)", r.ph, r.vOcpMv, r.tempC)
                phResult = r
            }

        case .temperature(let t):
            tempC = t

        case .refFrame(let mode, let rtiaIdx):
            refMode = mode
            refRtia = rtiaIdx
            let modeName: String
            switch mode {
            case 0: modeName = "EIS"
            case 1: modeName = "LSV"
            case 2: modeName = "Amp"
            case 3: modeName = "Cl"
            case 4: modeName = "pH"
            default: modeName = "?"
            }
            if mode == 0 {
                status = "Ref: \(modeName) RTIA \(rtiaIdx + 1)/8"
            } else {
                status = "Ref: \(modeName) range search"
            }

        case .refLpRange:
            break

        case .refsDone:
            transport.measuring = false
            collectingRefs = false
            hasDeviceRefs = true
            refMode = nil
            refRtia = nil
            let idx = Int(rtia.rawValue)
            if idx < 8, let pts = eisRefs[idx] {
                eisRef = pts
            }
            status = "Reference collection complete"

        case .refStatus(let hasRefs):
            hasDeviceRefs = hasRefs
            if !hasRefs {
                status = "No device refs"
            }

        case .cellK(let k):
            calCellConstant = Double(k)
            status = String(format: "Device cell constant: %.4f cm\u{207B}\u{00B9}", k)

        case .clFactor(let f):
            clFactor = Double(f)
            status = String(format: "Device Cl factor: %.6f", f)

        case .phCal(let slope, let offset):
            phSlope = Double(slope)
            phOffset = Double(offset)
            status = String(format: "pH cal: slope=%.4f offset=%.4f", slope, offset)

        case .sessionCreated(let fwId, let name):
            handleSessionCreated(fwId: fwId, name: name)

        case .sessionSwitched(let fwId):
            handleSessionSwitched(fwId: fwId)

        case .sessionList(_, let currentId, let sessions):
            handleSessionList(currentId: currentId, sessions: sessions)

        case .sessionRenamed(let fwId, let name):
            handleSessionRenamed(fwId: fwId, name: name)

        case .keepalive:
            break
        }
    }

    // MARK: - Session sync

    private func handleSessionCreated(fwId: UInt8, name: String) {
        let fwId64 = Int64(fwId)
        if let existing = Storage.shared.sessionByFirmwareId(fwId64) {
            firmwareSessionMap[fwId] = existing.id
        } else if let session = try? Storage.shared.createSession(
            label: name.isEmpty ? nil : name,
            firmwareSessionId: fwId64
        ) {
            firmwareSessionMap[fwId] = session.id
            currentSessionId = session.id
        }
    }

    private func handleSessionSwitched(fwId: UInt8) {
        if let localId = firmwareSessionMap[fwId] {
            currentSessionId = localId
        } else {
            let fwId64 = Int64(fwId)
            if let existing = Storage.shared.sessionByFirmwareId(fwId64) {
                firmwareSessionMap[fwId] = existing.id
                currentSessionId = existing.id
            }
        }
    }

    private func handleSessionList(currentId: UInt8, sessions: [(id: UInt8, name: String)]) {
        for entry in sessions {
            let fwId64 = Int64(entry.id)
            if let existing = Storage.shared.sessionByFirmwareId(fwId64) {
                firmwareSessionMap[entry.id] = existing.id
            } else if let session = try? Storage.shared.createSession(
                label: entry.name.isEmpty ? nil : entry.name,
                firmwareSessionId: fwId64
            ) {
                firmwareSessionMap[entry.id] = session.id
            }
        }
        handleSessionSwitched(fwId: currentId)
    }

    private func handleSessionRenamed(fwId: UInt8, name: String) {
        guard let localId = firmwareSessionMap[fwId] else { return }
        try? Storage.shared.updateSessionLabel(localId, label: name)
    }

    // MARK: - Actions

    func applyEISSettings() {
        let fs = Float(freqStart) ?? 1000
        let fe = Float(freqStop) ?? 200000
        let p = UInt16(ppd) ?? 10
        send(buildSysexSetSweep(freqStart: fs, freqStop: fe, ppd: p))
        send(buildSysexSetRtia(rtia))
        send(buildSysexSetRcal(rcal))
        send(buildSysexSetElectrode(electrode))
        send(buildSysexGetConfig())
    }

    func startSweep() {
        eisPoints.removeAll()
        send(buildSysexGetTemp())
        send(buildSysexStartSweep())
    }

    func lsvCalcPoints() -> UInt16 {
        let vs = Float(lsvStartV) ?? 0
        let ve = Float(lsvStopV) ?? 500
        let sr = Float(lsvScanRate) ?? 50
        let d = Float(lsvDensity) ?? 1
        let range = abs(ve - vs)
        let raw: Float
        switch lsvDensityMode {
        case .ptsPerMv:
            raw = range * d
        case .ptsPerSec:
            raw = abs(sr) < 0.001 ? 2 : (range / abs(sr)) * d
        }
        return max(2, min(500, UInt16(raw)))
    }

    func startLSV() {
        lsvPoints.removeAll()
        let vs = Float(lsvStartV) ?? 0
        let ve = Float(lsvStopV) ?? 500
        let sr = Float(lsvScanRate) ?? 50
        let n = lsvCalcPoints()
        send(buildSysexGetTemp())
        send(buildSysexStartLsv(vStart: vs, vStop: ve, scanRate: sr, lpRtia: lsvRtia, numPoints: n))
    }

    func startAmp() {
        ampPoints.removeAll()
        ampRunning = true
        let vh = Float(ampVHold) ?? 200
        let iv = Float(ampInterval) ?? 100
        let dur = Float(ampDuration) ?? 60
        send(buildSysexGetTemp())
        send(buildSysexStartAmp(vHold: vh, intervalMs: iv, durationS: dur, lpRtia: ampRtia))
    }

    func stopAmp() {
        send(buildSysexStopAmp())
    }

    func startChlorine() {
        clPoints.removeAll()
        clResult = nil
        let vCond = Float(clCondV) ?? 800
        let tCond = Float(clCondT) ?? 2000
        let vFree = Float(clFreeV) ?? 100
        let vTotal = Float(clTotalV) ?? -200
        let tDep = Float(clDepT) ?? 5000
        let tMeas = Float(clMeasT) ?? 5000
        send(buildSysexGetTemp())
        send(buildSysexStartCl(
            vCond: vCond, tCondMs: tCond, vFree: vFree, vTotal: vTotal,
            tDepMs: tDep, tMeasMs: tMeas, lpRtia: clRtia
        ))
    }

    func lsvCalcPointsFor(vStart: Float, vStop: Float, scanRate: Float) -> UInt16 {
        let d = Float(lsvDensity) ?? 1
        let range = abs(vStop - vStart)
        let raw: Float
        switch lsvDensityMode {
        case .ptsPerMv:
            raw = range * d
        case .ptsPerSec:
            raw = abs(scanRate) < 0.001 ? 2 : (range / abs(scanRate)) * d
        }
        return max(2, min(500, UInt16(raw)))
    }

    func startClAuto() {
        clAutoState = .lsvRunning
        clAutoPotentials = nil
        lsvPoints.removeAll()
        let n = lsvCalcPointsFor(vStart: -1100, vStop: 1100, scanRate: 50)
        send(buildSysexStartLsv(vStart: -1100, vStop: 1100, scanRate: 50, lpRtia: lsvRtia, numPoints: n))
        status = "Auto Cl: running LSV sweep..."
    }

    func startPh() {
        phResult = nil
        transport.measuring = true
        let stab = Float(phStabilize) ?? 30
        send(buildSysexGetTemp())
        send(buildSysexStartPh(stabilizeS: stab))
    }

    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 .calibrate, .sessions, .connection: break
        }
    }

    func collectRefs() {
        collectingRefs = true
        transport.measuring = true
        eisRefs.removeAll()
        status = "Starting reference collection..."
        send(buildSysexStartRefs())
    }

    func getRefs() {
        collectingRefs = true
        transport.measuring = true
        eisRefs.removeAll()
        send(buildSysexGetRefs())
    }

    func clearRefs() {
        collectingRefs = false
        hasDeviceRefs = false
        eisRefs.removeAll()
        eisRef = nil
        phRef = nil
        send(buildSysexClearRefs())
        status = "Refs cleared"
    }

    func startClean() {
        let v = Float(cleanV) ?? 1200
        let d = Float(cleanDur) ?? 30
        transport.measuring = true
        send(buildSysexStartClean(vMv: v, durationS: d))
        status = String(format: "Cleaning: %.0f mV for %.0fs", v, d)
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(d) + 2) { [weak self] in
            self?.transport.measuring = false
        }
    }

    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 .calibrate, .sessions, .connection: 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 .calibrate, .sessions, .connection: false
        }
    }

    // MARK: - Auto-save

    private func saveEis() {
        guard let sid = currentSessionId else { return }
        let ts = pendingEspTimestamp
        pendingEspTimestamp = nil
        let params: [String: String] = [
            "freq_start": freqStart,
            "freq_stop": freqStop,
            "ppd": ppd,
            "rtia": rtia.label,
            "rcal": rcal.label,
            "electrode": electrode.label,
        ]
        guard let configData = try? JSONEncoder().encode(params) else { return }
        guard var meas = try? Storage.shared.addMeasurement(sessionId: sid, type: .eis, espTimestamp: ts) else { return }
        meas.config = configData
        guard let mid = meas.id else { return }
        let indexed = eisPoints.enumerated().map { (index: $0.offset, value: $0.element) }
        try? Storage.shared.addDataPoints(measurementId: mid, points: indexed)
    }

    private func saveLsv() {
        guard let sid = currentSessionId else { return }
        let ts = pendingEspTimestamp
        pendingEspTimestamp = nil
        let params: [String: String] = [
            "v_start": lsvStartV,
            "v_stop": lsvStopV,
            "scan_rate": lsvScanRate,
            "rtia": lsvRtia.label,
        ]
        guard let configData = try? JSONEncoder().encode(params) else { return }
        guard var meas = try? Storage.shared.addMeasurement(sessionId: sid, type: .lsv, espTimestamp: ts) else { return }
        meas.config = configData
        guard let mid = meas.id else { return }
        let indexed = lsvPoints.enumerated().map { (index: $0.offset, value: $0.element) }
        try? Storage.shared.addDataPoints(measurementId: mid, points: indexed)
    }

    private func saveAmp() {
        guard let sid = currentSessionId else { return }
        let ts = pendingEspTimestamp
        pendingEspTimestamp = nil
        let params: [String: String] = [
            "v_hold": ampVHold,
            "interval_ms": ampInterval,
            "duration_s": ampDuration,
            "rtia": ampRtia.label,
        ]
        guard let configData = try? JSONEncoder().encode(params) else { return }
        guard var meas = try? Storage.shared.addMeasurement(sessionId: sid, type: .amp, espTimestamp: ts) else { return }
        meas.config = configData
        guard let mid = meas.id else { return }
        let indexed = ampPoints.enumerated().map { (index: $0.offset, value: $0.element) }
        try? Storage.shared.addDataPoints(measurementId: mid, points: indexed)
    }

    private func saveCl() {
        guard let sid = currentSessionId else { return }
        let ts = pendingEspTimestamp
        pendingEspTimestamp = nil
        let params: [String: String] = [
            "cond_v": clCondV,
            "cond_t": clCondT,
            "free_v": clFreeV,
            "total_v": clTotalV,
            "dep_t": clDepT,
            "meas_t": clMeasT,
            "rtia": clRtia.label,
        ]
        guard let configData = try? JSONEncoder().encode(params) else { return }
        guard var meas = try? Storage.shared.addMeasurement(sessionId: sid, type: .chlorine, espTimestamp: ts) else { return }
        meas.config = configData
        guard let mid = meas.id else { return }
        let indexed = clPoints.enumerated().map { (index: $0.offset, value: $0.element) }
        try? Storage.shared.addDataPoints(measurementId: mid, points: indexed)
        if let r = clResult {
            try? Storage.shared.setMeasurementResult(mid, result: r)
        }
    }

    private func savePh(_ result: PhResult) {
        guard let sid = currentSessionId else { return }
        let ts = pendingEspTimestamp
        pendingEspTimestamp = nil
        let params: [String: String] = [
            "stabilize_s": phStabilize,
        ]
        guard let configData = try? JSONEncoder().encode(params) else { return }
        guard var meas = try? Storage.shared.addMeasurement(sessionId: sid, type: .ph, espTimestamp: ts) else { return }
        meas.config = configData
        guard let mid = meas.id else { return }
        try? Storage.shared.addDataPoint(measurementId: mid, index: 0, point: result)
        try? Storage.shared.setMeasurementResult(mid, result: result)
    }

    // 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)"
        }
    }
}
