USB-Meter / USB Meter / Views / Meter / Tabs / ChargeRecord / MeterChargeRecordTabView.swift
1 contributor
951 lines | 37.893kb
//
//  MeterChargeRecordTabView.swift
//  USB Meter
//

import SwiftUI

struct MeterChargeRecordTabView: View {
    var body: some View {
        MeterChargeRecordContentView()
    }
}

struct MeterChargeRecordContentView: View {
    @EnvironmentObject private var appData: AppData
    @EnvironmentObject private var usbMeter: Meter

    @State private var chargedDeviceLibraryVisibility = false
    @State private var chargerLibraryVisibility = false
    @State private var checkpointEditorVisibility = false
    @State private var editingChargedDevice: ChargedDeviceSummary?
    @State private var targetNotificationEditorVisibility = false
    @State private var pendingStopRequest: ChargeSessionStopRequest?
    @State private var draftChargingTransportMode: ChargingTransportMode?
    @State private var draftChargingStateMode: ChargingStateMode?
    @State private var draftAutoStopEnabled = true
    @State private var initialCheckpoint = ""

    var body: some View {
        ScrollView {
            VStack(spacing: 16) {
                headerCard
                sessionSetupCard

                if let openChargeSession {
                    chargingMonitorCard(openChargeSession)

                    if let sessionChartTimeRange {
                        sessionChartCard(timeRange: sessionChartTimeRange, session: openChargeSession)
                    }
                }

                if usbMeter.supportsDataGroupCommands || usbMeter.recordedAH > 0 || usbMeter.recordedWH > 0 || usbMeter.recordingDuration > 0 {
                    meterTotalsCard
                }
            }
            .padding()
        }
        .background(
            LinearGradient(
                colors: [.pink.opacity(0.14), Color.clear],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
            .ignoresSafeArea()
        )
        .sheet(isPresented: $chargedDeviceLibraryVisibility) {
            ChargedDeviceLibrarySheetView(
                visibility: $chargedDeviceLibraryVisibility,
                meterMACAddress: meterMACAddress,
                meterTint: usbMeter.color,
                mode: .device
            )
            .environmentObject(appData)
        }
        .sheet(isPresented: $chargerLibraryVisibility) {
            ChargedDeviceLibrarySheetView(
                visibility: $chargerLibraryVisibility,
                meterMACAddress: meterMACAddress,
                meterTint: usbMeter.color,
                mode: .charger
            )
            .environmentObject(appData)
        }
        .sheet(isPresented: $checkpointEditorVisibility) {
            BatteryCheckpointEditorSheetView()
                .environmentObject(appData)
                .environmentObject(usbMeter)
        }
        .sheet(item: $editingChargedDevice) { chargedDevice in
            ChargedDeviceEditorSheetView(
                meterMACAddress: nil,
                chargedDevice: chargedDevice
            )
            .environmentObject(appData)
        }
        .sheet(isPresented: $targetNotificationEditorVisibility) {
            if let openChargeSession {
                BatteryTargetNotificationEditorSheetView(
                    sessionID: openChargeSession.id,
                    initialTargetPercent: openChargeSession.targetBatteryPercent
                )
                .environmentObject(appData)
            }
        }
        .sheet(item: $pendingStopRequest) { request in
            ChargeSessionCompletionSheetView(
                sessionID: request.sessionID,
                title: request.title,
                confirmTitle: request.confirmTitle,
                explanation: request.explanation
            )
            .environmentObject(appData)
        }
        .onAppear {
            syncDraftSelections()
        }
        .onChange(of: selectedChargedDevice?.id) { _ in
            syncDraftSelections()
        }
        .onChange(of: openChargeSession?.id) { _ in
            syncDraftSelections()
        }
    }

    private var meterMACAddress: String {
        usbMeter.btSerial.macAddress.description
    }

    private var selectedChargedDevice: ChargedDeviceSummary? {
        appData.currentChargedDeviceSummary(for: meterMACAddress)
    }

    private var selectedCharger: ChargedDeviceSummary? {
        appData.currentChargerSummary(for: meterMACAddress)
    }

    private var openChargeSession: ChargeSessionSummary? {
        appData.activeChargeSessionSummary(for: meterMACAddress)
    }

    private var selectedDraftTransportMode: ChargingTransportMode? {
        openChargeSession?.chargingTransportMode ?? draftChargingTransportMode
    }

    private var selectedDraftChargingStateMode: ChargingStateMode? {
        openChargeSession?.chargingStateMode ?? draftChargingStateMode
    }

    private var selectedDraftSessionKind: ChargeSessionKind? {
        guard let chargingTransportMode = selectedDraftTransportMode,
              let chargingStateMode = selectedDraftChargingStateMode else {
            return nil
        }

        return ChargeSessionKind(
            chargingTransportMode: chargingTransportMode,
            chargingStateMode: chargingStateMode
        )
    }

    private var selectedDraftStopThreshold: Double? {
        guard let selectedChargedDevice,
              let chargingTransportMode = selectedDraftTransportMode else {
            return nil
        }

        return selectedChargedDevice.resolvedCompletionCurrentAmps(
            for: chargingTransportMode,
            chargingStateMode: selectedDraftChargingStateMode
        )
    }

    private var initialCheckpointValue: Double? {
        let normalized = initialCheckpoint
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .replacingOccurrences(of: ",", with: ".")
        guard let value = Double(normalized), value >= 0, value <= 100 else {
            return nil
        }
        return value
    }

    private var requiresExplicitTransportSelection: Bool {
        (selectedChargedDevice?.supportedChargingModes.count ?? 0) > 1
    }

    private var requiresExplicitChargingStateSelection: Bool {
        (selectedChargedDevice?.supportedChargingStateModes.count ?? 0) > 1
    }

    private var canStartSession: Bool {
        guard openChargeSession == nil,
              let selectedChargedDevice,
              let chargingTransportMode = selectedDraftTransportMode,
              let chargingStateMode = selectedDraftChargingStateMode,
              let initialCheckpointValue else {
            return false
        }

        guard selectedChargedDevice.supportedChargingModes.contains(chargingTransportMode) else {
            return false
        }

        guard selectedChargedDevice.supportedChargingStateModes.contains(chargingStateMode) else {
            return false
        }

        if chargingTransportMode == .wireless {
            return selectedCharger != nil
        }

        return true
    }

    private var headerStatusTitle: String {
        guard let openChargeSession else {
            return "Idle"
        }
        return openChargeSession.status.title
    }

    private var headerStatusColor: Color {
        guard let openChargeSession else {
            return .secondary
        }

        switch openChargeSession.status {
        case .active:
            return .red
        case .paused:
            return .orange
        case .completed:
            return .green
        case .abandoned:
            return .secondary
        }
    }

    private var sessionChartTimeRange: ClosedRange<Date>? {
        guard let openChargeSession else {
            return nil
        }

        let end = openChargeSession.pausedAt ?? openChargeSession.lastObservedAt
        return openChargeSession.startedAt...max(end, openChargeSession.startedAt)
    }

    private var draftAutoStopDescription: String {
        guard let chargingTransportMode = selectedDraftTransportMode else {
            return "Choose the charging type before starting the session."
        }

        if chargingTransportMode == .wireless, selectedCharger == nil {
            return "Wireless sessions need a selected charger before they can start."
        }

        if draftAutoStopEnabled == false {
            return "The session starts open-ended and will stop only when you pause or stop it manually."
        }

        if let setupWarning = setupWirelessThresholdWarning {
            return setupWarning
        }

        if let selectedDraftSessionKind, let selectedDraftStopThreshold {
            return "Auto-stop is ready for \(selectedDraftSessionKind.shortTitle.lowercased()) sessions at about \(selectedDraftStopThreshold.format(decimalDigits: 2)) A."
        }

        return "No stop threshold is known for this charging type yet, so the session starts open-ended."
    }

    private var setupWirelessThresholdWarning: String? {
        guard selectedDraftTransportMode == .wireless else {
            return nil
        }

        guard let selectedCharger else {
            return nil
        }

        guard selectedCharger.chargerIdleCurrentAmps == nil else {
            return nil
        }

        return "This charger has no idle-current measurement. Wireless sessions can still be recorded, but they cannot learn or auto-apply the final stop threshold yet."
    }

    private var headerCard: some View {
        VStack(alignment: .leading, spacing: 8) {
            HStack {
                Text("Charging Session")
                    .font(.system(.title3, design: .rounded).weight(.bold))
                Spacer()
                Text(headerStatusTitle)
                    .font(.caption.weight(.bold))
                    .foregroundColor(headerStatusColor)
                    .padding(.horizontal, 10)
                    .padding(.vertical, 6)
                    .meterCard(
                        tint: headerStatusColor,
                        fillOpacity: 0.18,
                        strokeOpacity: 0.24,
                        cornerRadius: 999
                    )
            }

            Text("Recording starts only after the device, charging type, charging mode, and initial checkpoint are explicit.")
                .font(.footnote)
                .foregroundColor(.secondary)
        }
        .frame(maxWidth: .infinity)
        .padding(18)
        .meterCard(tint: .pink, fillOpacity: 0.18, strokeOpacity: 0.24)
    }

    private var sessionSetupCard: some View {
        VStack(alignment: .leading, spacing: 14) {
            HStack {
                Text(openChargeSession == nil ? "Session Setup" : "Session Context")
                    .font(.headline)
                Spacer()
                Button("Library") {
                    chargedDeviceLibraryVisibility = true
                }
                .disabled(openChargeSession != nil)
            }

            if let selectedChargedDevice {
                deviceSummary(selectedChargedDevice)

                if openChargeSession == nil {
                    setupControls(for: selectedChargedDevice)
                }

                Button("Edit Device") {
                    editingChargedDevice = selectedChargedDevice
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .meterCard(tint: .blue, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
                .buttonStyle(.plain)
            } else {
                Text("Select or create the device you are charging. Sessions, checkpoints, curve learning, and wireless charger warnings all depend on that explicit selection.")
                    .font(.footnote)
                    .foregroundColor(.secondary)
            }
        }
        .padding(18)
        .meterCard(tint: .orange, fillOpacity: 0.14, strokeOpacity: 0.20)
    }

    private func deviceSummary(_ chargedDevice: ChargedDeviceSummary) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack(alignment: .top, spacing: 14) {
                ChargedDeviceQRCodeView(
                    qrIdentifier: chargedDevice.qrIdentifier,
                    side: 88
                )

                VStack(alignment: .leading, spacing: 8) {
                    Label(chargedDevice.name, systemImage: chargedDevice.deviceClass.symbolName)
                        .font(.headline)

                    Text(chargedDevice.deviceClass.title)
                        .font(.caption.weight(.semibold))
                        .foregroundColor(.secondary)

                    Text(chargedDevice.chargingStateAvailability.description)
                        .font(.caption2)
                        .foregroundColor(.secondary)

                    Text(chargedDevice.supportedChargingModes.map(\.title).joined(separator: " + "))
                        .font(.caption2)
                        .foregroundColor(.secondary)

                    if let selectedDraftSessionKind,
                       let threshold = chargedDevice.resolvedCompletionCurrentAmps(
                        for: selectedDraftSessionKind.chargingTransportMode,
                        chargingStateMode: selectedDraftSessionKind.chargingStateMode
                       ) {
                        Text("\(selectedDraftSessionKind.shortTitle) stop current: \(threshold.format(decimalDigits: 2)) A")
                            .font(.caption2)
                            .foregroundColor(.secondary)
                    } else if let estimatedCapacity = chargedDevice.estimatedBatteryCapacityWh(
                        for: chargedDevice.preferredChargingTransportMode
                    ) {
                        Text("Estimated \(chargedDevice.preferredChargingTransportMode.title.lowercased()) capacity: \(estimatedCapacity.format(decimalDigits: 2)) Wh")
                            .font(.caption2)
                            .foregroundColor(.secondary)
                    }
                }

                Spacer(minLength: 0)
            }

            if showsWirelessChargerSection {
                Divider()
                wirelessChargerSection
            }
        }
    }

    private func setupControls(for chargedDevice: ChargedDeviceSummary) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            if requiresExplicitTransportSelection {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Charging Type")
                        .font(.subheadline.weight(.semibold))

                    Picker("Charging Type", selection: $draftChargingTransportMode) {
                        ForEach(chargedDevice.supportedChargingModes) { chargingTransportMode in
                            Label(chargingTransportMode.title, systemImage: chargingTransportMode.symbolName)
                                .tag(Optional(chargingTransportMode))
                        }
                    }
                    .pickerStyle(.segmented)

                    if draftChargingTransportMode == nil {
                        Text("Pick the charging type explicitly before starting.")
                            .font(.caption2)
                            .foregroundColor(.orange)
                    }
                }
            } else if let chargingTransportMode = chargedDevice.supportedChargingModes.first {
                Label(
                    "Charging type: \(chargingTransportMode.title)",
                    systemImage: chargingTransportMode.symbolName
                )
                .font(.subheadline.weight(.semibold))
            }

            if requiresExplicitChargingStateSelection {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Charging Mode")
                        .font(.subheadline.weight(.semibold))

                    Picker("Charging Mode", selection: $draftChargingStateMode) {
                        ForEach(chargedDevice.supportedChargingStateModes) { chargingStateMode in
                            Text(chargingStateMode.title)
                                .tag(Optional(chargingStateMode))
                        }
                    }
                    .pickerStyle(.segmented)

                    if draftChargingStateMode == nil {
                        Text("Pick whether the device is on or off for this session.")
                            .font(.caption2)
                            .foregroundColor(.orange)
                    }
                }
            } else if let chargingStateMode = chargedDevice.supportedChargingStateModes.first {
                Label(
                    "Charging mode: \(chargingStateMode.title)",
                    systemImage: chargingStateMode == .off ? "power.circle" : "power"
                )
                .font(.subheadline.weight(.semibold))
            }

            VStack(alignment: .leading, spacing: 8) {
                Text("Initial Checkpoint")
                    .font(.subheadline.weight(.semibold))

                TextField("Battery %", text: $initialCheckpoint)
                    .keyboardType(.decimalPad)

                Text("The session starts only after this first checkpoint is recorded.")
                    .font(.caption2)
                    .foregroundColor(.secondary)
            }

            Toggle("Auto-stop when the type already has a stop threshold", isOn: $draftAutoStopEnabled)

            Text(draftAutoStopDescription)
                .font(.footnote)
                .foregroundColor(setupWirelessThresholdWarning == nil ? .secondary : .orange)

            Button("Start Session") {
                startSession()
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: .green, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)
            .disabled(!canStartSession)
        }
    }

    private var wirelessChargerSection: some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack {
                Text("Wireless Charger")
                    .font(.subheadline.weight(.semibold))
                Spacer()
                Button(selectedCharger == nil ? "Select" : "Change") {
                    chargerLibraryVisibility = true
                }
                .disabled(openChargeSession != nil)
            }

            if let selectedCharger {
                HStack(alignment: .top, spacing: 12) {
                    ChargedDeviceQRCodeView(
                        qrIdentifier: selectedCharger.qrIdentifier,
                        side: 62
                    )

                    VStack(alignment: .leading, spacing: 6) {
                        Label(selectedCharger.name, systemImage: selectedCharger.deviceClass.symbolName)
                            .font(.subheadline.weight(.semibold))

                        if let chargerMaximumPowerWatts = selectedCharger.chargerMaximumPowerWatts {
                            Text("Max power: \(chargerMaximumPowerWatts.format(decimalDigits: 2)) W")
                                .font(.caption)
                                .foregroundColor(.secondary)
                        }

                        if let chargerIdleCurrentAmps = selectedCharger.chargerIdleCurrentAmps {
                            Text("Idle current: \(chargerIdleCurrentAmps.format(decimalDigits: 2)) A")
                                .font(.caption2)
                                .foregroundColor(.secondary)
                        } else {
                            Text("Idle current is missing, so wireless stop-threshold learning and auto-stop stay unavailable.")
                                .font(.caption2)
                                .foregroundColor(.orange)
                        }
                    }
                }
            } else {
                Text("Wireless sessions need a selected charger in addition to the charged device.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
        }
    }

    private func chargingMonitorCard(_ openChargeSession: ChargeSessionSummary) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Charging Monitor")
                .font(.headline)

            ChargeRecordMetricsTableView(
                labels: ["Type", "Mode", "Energy", "Auto Stop"],
                values: [
                    openChargeSession.chargingTransportMode.title,
                    openChargeSession.chargingStateMode.title,
                    "\(openChargeSession.effectiveOrMeasuredEnergyWh.format(decimalDigits: 3)) Wh",
                    autoStopLabel(for: openChargeSession)
                ]
            )

            if let batteryPrediction = selectedChargedDevice?.batteryLevelPrediction(for: openChargeSession) {
                VStack(alignment: .leading, spacing: 4) {
                    Text("Predicted battery level: \(batteryPrediction.predictedPercent.format(decimalDigits: 0))%")
                        .font(.caption.weight(.semibold))
                    Text(
                        "Anchored to \(batteryPrediction.anchorDescription) at \(batteryPrediction.anchorPercent.format(decimalDigits: 0))% using \(batteryPrediction.estimatedCapacityWh.format(decimalDigits: 2)) Wh estimated capacity."
                    )
                    .font(.caption2)
                    .foregroundColor(.secondary)
                }
            }

            if let sessionWarning = sessionWarning(for: openChargeSession) {
                Text(sessionWarning)
                    .font(.caption)
                    .foregroundColor(.orange)
            }

            if openChargeSession.isPaused {
                Text("Paused at \(openChargeSession.pausedAt?.format() ?? openChargeSession.lastObservedAt.format()). If it stays paused for more than 10 minutes, it stops automatically.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            if openChargeSession.requiresCompletionConfirmation {
                completionConfirmationCard(openChargeSession)
            }

            if let targetBatteryPercent = openChargeSession.targetBatteryPercent {
                Text("Target notification: \(targetBatteryPercent.format(decimalDigits: 0))%")
                    .font(.caption.weight(.semibold))
            } else {
                Text("No target battery notification configured.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            Button("Add Battery Checkpoint") {
                checkpointEditorVisibility = true
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: .green, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)

            Button(openChargeSession.targetBatteryPercent == nil ? "Set Target Notification" : "Change Target Notification") {
                targetNotificationEditorVisibility = true
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: .indigo, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)

            if openChargeSession.targetBatteryPercent != nil {
                Button("Clear Target Notification") {
                    _ = appData.setTargetBatteryPercent(nil, for: openChargeSession.id)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .meterCard(tint: .secondary, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 14)
                .buttonStyle(.plain)
            }

            if openChargeSession.status == .active {
                Button("Pause Session") {
                    _ = appData.pauseChargeSession(sessionID: openChargeSession.id, from: usbMeter)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .meterCard(tint: .orange, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
                .buttonStyle(.plain)
            } else if openChargeSession.status == .paused {
                Button("Resume Session") {
                    _ = appData.resumeChargeSession(sessionID: openChargeSession.id, from: usbMeter)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .meterCard(tint: .blue, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
                .buttonStyle(.plain)
            }

            Button("Stop Session") {
                pendingStopRequest = ChargeSessionStopRequest(
                    sessionID: openChargeSession.id,
                    title: "Stop Session",
                    confirmTitle: "Stop",
                    explanation: "Record the final battery checkpoint before closing this session."
                )
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: .red, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)

            if !openChargeSession.checkpoints.isEmpty {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Battery Checkpoints")
                        .font(.subheadline.weight(.semibold))

                    ForEach(openChargeSession.checkpoints.suffix(4), id: \.id) { checkpoint in
                        HStack {
                            Text(checkpoint.timestamp.format())
                                .font(.caption2)
                                .foregroundColor(.secondary)
                            Spacer()
                            Text("\(checkpoint.batteryPercent.format(decimalDigits: 0))%")
                                .font(.caption.weight(.semibold))
                            Text("•")
                                .foregroundColor(.secondary)
                            Text("\(checkpoint.measuredEnergyWh.format(decimalDigits: 2)) Wh")
                                .font(.caption2)
                                .foregroundColor(.secondary)
                        }
                    }
                }
            }

            Text("The monitor keeps the session explicit: it never auto-selects another device or charger, and it never starts a new session on its own.")
                .font(.footnote)
                .foregroundColor(.secondary)
        }
        .padding(18)
        .meterCard(tint: .green, fillOpacity: 0.14, strokeOpacity: 0.20)
    }

    private func completionConfirmationCard(_ openChargeSession: ChargeSessionSummary) -> some View {
        VStack(alignment: .leading, spacing: 10) {
            Text("Completion Needs Confirmation")
                .font(.subheadline.weight(.semibold))

            if let contradictionPercent = openChargeSession.completionContradictionPercent {
                Text("Current says charging may have stopped, but the estimated battery level is only \(contradictionPercent.format(decimalDigits: 0))%.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            } else {
                Text("Current dropped to the learned stop threshold, but the battery prediction does not look like a normal finish yet.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            Button("Finish Session With Final Checkpoint") {
                pendingStopRequest = ChargeSessionStopRequest(
                    sessionID: openChargeSession.id,
                    title: "Finish Session",
                    confirmTitle: "Finish",
                    explanation: "Add the final checkpoint before confirming the stop."
                )
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: .orange, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)

            Button("Keep Monitoring") {
                _ = appData.continueChargeSessionMonitoring(sessionID: openChargeSession.id)
            }
            .frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: .blue, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)
        }
        .padding(14)
        .meterCard(tint: .orange, fillOpacity: 0.10, strokeOpacity: 0.16, cornerRadius: 16)
    }

    private func sessionChartCard(
        timeRange: ClosedRange<Date>,
        session: ChargeSessionSummary
    ) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Session Chart")
                .font(.headline)

            MeasurementChartView(timeRange: timeRange)
                .environmentObject(usbMeter.measurements)
                .frame(minHeight: 220)

            Text("The chart is scoped to the explicit session window for \(session.sessionKind.shortTitle.lowercased()) charging.")
                .font(.footnote)
                .foregroundColor(.secondary)
        }
        .padding(18)
        .meterCard(tint: .blue, fillOpacity: 0.14, strokeOpacity: 0.20)
    }

    private var meterTotalsCard: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Meter Totals")
                .font(.headline)

            ChargeRecordMetricsTableView(
                labels: ["Capacity", "Energy", "Duration", "Meter Threshold"],
                values: [
                    "\(usbMeter.recordedAH.format(decimalDigits: 3)) Ah",
                    "\(usbMeter.recordedWH.format(decimalDigits: 3)) Wh",
                    usbMeter.recordingDurationDescription,
                    usbMeter.supportsRecordingThreshold ? "\(usbMeter.recordingTreshold.format(decimalDigits: 2)) A" : "Read-only"
                ]
            )

            Text("These values come directly from the meter and remain separate from the explicit app session controls.")
                .font(.footnote)
                .foregroundColor(.secondary)

            if usbMeter.supportsDataGroupCommands {
                Button("Reset Active Group") {
                    usbMeter.clear()
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
                .meterCard(tint: .red, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
                .buttonStyle(.plain)
            }
        }
        .padding(18)
        .meterCard(tint: .teal, fillOpacity: 0.14, strokeOpacity: 0.20)
    }

    private var showsWirelessChargerSection: Bool {
        let transportMode = selectedDraftTransportMode ?? selectedChargedDevice?.supportedChargingModes.first
        return transportMode == .wireless
    }

    private func autoStopLabel(for session: ChargeSessionSummary) -> String {
        if session.autoStopEnabled == false {
            return "Manual"
        }
        if let sessionWarning = sessionWarning(for: session), session.chargingTransportMode == .wireless {
            return sessionWarning.contains("idle-current") ? "Blocked" : "Manual"
        }
        if session.stopThresholdAmps > 0 {
            return "\(session.stopThresholdAmps.format(decimalDigits: 2)) A"
        }
        return "Learning"
    }

    private func sessionWarning(for session: ChargeSessionSummary) -> String? {
        guard session.chargingTransportMode == .wireless,
              let chargerID = session.chargerID,
              let charger = appData.chargedDeviceSummary(id: chargerID) else {
            return nil
        }

        guard charger.chargerIdleCurrentAmps == nil else {
            return nil
        }

        return "The selected charger has no idle-current measurement. Wireless stop-threshold learning and precise auto-stop are unavailable for this session."
    }

    private func startSession() {
        guard let selectedChargedDevice,
              let chargingTransportMode = selectedDraftTransportMode,
              let chargingStateMode = selectedDraftChargingStateMode,
              let initialCheckpointValue else {
            return
        }

        let chargerID = chargingTransportMode == .wireless ? selectedCharger?.id : nil
        let didStart = appData.startChargeSession(
            for: usbMeter,
            chargedDeviceID: selectedChargedDevice.id,
            chargerID: chargerID,
            chargingTransportMode: chargingTransportMode,
            chargingStateMode: chargingStateMode,
            autoStopEnabled: draftAutoStopEnabled,
            initialBatteryPercent: initialCheckpointValue
        )

        if didStart {
            initialCheckpoint = ""
        }
    }

    private func syncDraftSelections() {
        guard let selectedChargedDevice else {
            draftChargingTransportMode = nil
            draftChargingStateMode = nil
            draftAutoStopEnabled = true
            return
        }

        if let openChargeSession {
            draftChargingTransportMode = openChargeSession.chargingTransportMode
            draftChargingStateMode = openChargeSession.chargingStateMode
            draftAutoStopEnabled = openChargeSession.autoStopEnabled
            return
        }

        if let draftChargingTransportMode,
           selectedChargedDevice.supportedChargingModes.contains(draftChargingTransportMode) == false {
            self.draftChargingTransportMode = nil
        }

        if let draftChargingStateMode,
           selectedChargedDevice.supportedChargingStateModes.contains(draftChargingStateMode) == false {
            self.draftChargingStateMode = nil
        }

        if selectedChargedDevice.supportedChargingModes.count == 1 {
            draftChargingTransportMode = selectedChargedDevice.supportedChargingModes.first
        }

        if selectedChargedDevice.supportedChargingStateModes.count == 1 {
            draftChargingStateMode = selectedChargedDevice.supportedChargingStateModes.first
        }
    }
}

struct ChargeSessionCompletionSheetView: View {
    @EnvironmentObject private var appData: AppData
    @Environment(\.dismiss) private var dismiss

    let sessionID: UUID
    let title: String
    let confirmTitle: String
    let explanation: String

    @State private var batteryPercent = ""
    @State private var label = "Final"

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Final Checkpoint")) {
                    TextField("Battery %", text: $batteryPercent)
                        .keyboardType(.decimalPad)
                    TextField("Label", text: $label)
                }

                Section {
                    Text(explanation)
                        .font(.footnote)
                        .foregroundColor(.secondary)

                    if let sessionWarning {
                        Text(sessionWarning)
                            .font(.footnote)
                            .foregroundColor(.orange)
                    } else if (parsedBatteryPercent ?? 0) >= 99.5 {
                        Text("A final checkpoint at 100% lets the app learn the stop current for this exact charging type when the session data is reliable.")
                            .font(.footnote)
                            .foregroundColor(.secondary)
                    }
                }
            }
            .navigationTitle(title)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        dismiss()
                    }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button(confirmTitle) {
                        guard let batteryPercent = parsedBatteryPercent else {
                            return
                        }

                        if appData.stopChargeSession(
                            sessionID: sessionID,
                            finalBatteryPercent: batteryPercent,
                            label: label.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "Final" : label
                        ) {
                            dismiss()
                        }
                    }
                    .disabled(parsedBatteryPercent == nil)
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }

    private var parsedBatteryPercent: Double? {
        let normalized = batteryPercent
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .replacingOccurrences(of: ",", with: ".")
        guard let value = Double(normalized), value >= 0, value <= 100 else {
            return nil
        }
        return value
    }

    private var sessionWarning: String? {
        guard let session = appData.chargedDevices
            .flatMap(\.sessions)
            .first(where: { $0.id == sessionID }),
              session.chargingTransportMode == .wireless,
              let chargerID = session.chargerID,
              let charger = appData.chargedDeviceSummary(id: chargerID),
              charger.chargerIdleCurrentAmps == nil else {
            return nil
        }

        return "This charger has no idle-current measurement, so the final checkpoint will stop the session but will not learn a wireless stop threshold yet."
    }
}

private struct ChargeSessionStopRequest: Identifiable {
    let sessionID: UUID
    let title: String
    let confirmTitle: String
    let explanation: String

    var id: UUID {
        sessionID
    }
}