USB-Meter / USB Meter / Views / ChargedDevices / Details / ChargedDeviceSettingsView.swift
1 contributor
1099 lines | 43.36kb
//
//  ChargedDeviceSettingsView.swift
//  USB Meter
//
//  Created by Codex on 10/04/2026.
//

import SwiftUI

struct ChargedDeviceSettingsView: View {
    private enum DetailTab: Hashable {
        case overview
        case standby
        case sessions
        case trends
        case settings
    }

    @EnvironmentObject private var appData: AppData
    @Environment(\.dismiss) private var dismiss

    @State private var editorVisibility = false
    @State private var deleteConfirmationVisibility = false
    @State private var selectedTab: DetailTab = .overview
    @State private var sessionSelectMode = false
    @State private var selectedSessionIDs: Set<UUID> = []
    @State private var pendingBatchDeletion = false

    let chargedDeviceID: UUID

    var body: some View {
        Group {
            if let chargedDevice = appData.chargedDeviceSummary(id: chargedDeviceID) {
                tabbedDetailView(chargedDevice)
                .navigationTitle(chargedDevice.name)
                .navigationBarTitleDisplayMode(.inline)
            } else {
                Text("This device is no longer available.")
                    .foregroundColor(.secondary)
                    .navigationTitle("Device")
                    .navigationBarTitleDisplayMode(.inline)
            }
        }
        .sidebarToggleToolbarItem()
        .sheet(isPresented: $editorVisibility) {
            if let chargedDevice = appData.chargedDeviceSummary(id: chargedDeviceID) {
                if chargedDevice.isCharger {
                    ChargerEditorSheetView(chargedDevice: chargedDevice)
                        .environmentObject(appData)
                } else {
                    ChargedDeviceEditorSheetView(chargedDevice: chargedDevice)
                    .environmentObject(appData)
                }
            }
        }
        .confirmationDialog("Delete \(deletionTitle)?", isPresented: $deleteConfirmationVisibility, titleVisibility: .visible) {
            Button("Delete", role: .destructive) {
                if appData.deleteChargedDevice(id: chargedDeviceID) {
                    dismiss()
                }
            }
            Button("Cancel", role: .cancel) {}
        } message: {
            Text(deletionMessage)
        }
        .confirmationDialog(
            "Delete \(selectedSessionIDs.count) Session\(selectedSessionIDs.count == 1 ? "" : "s")?",
            isPresented: $pendingBatchDeletion,
            titleVisibility: .visible
        ) {
            Button("Delete", role: .destructive, action: deleteSelectedSessions)
            Button("Cancel", role: .cancel) {}
        } message: {
            Text("Deleting these sessions also recalculates capacity and every derived metric that used them.")
        }
    }

    private func tabbedDetailView(_ chargedDevice: ChargedDeviceSummary) -> some View {
        GeometryReader { proxy in
            let tabs = availableTabs(for: chargedDevice)
            let displayedTab = displayedTab(from: tabs)
            let tabBarPresentation = AdaptiveTabBarPresentation.standard(for: proxy.size)

            VStack(spacing: 0) {
                ChargedDeviceDetailTabBarView(
                    tabs: tabs,
                    selection: $selectedTab,
                    tint: tint(for: chargedDevice),
                    presentation: tabBarPresentation,
                    title: title(for:),
                    systemImage: systemImage(for:)
                )

                Group {
                    if displayedTab == .sessions {
                        sessionsTabLayout(chargedDevice)
                    } else {
                        ScrollView {
                            tabContent(displayedTab, chargedDevice: chargedDevice)
                                .padding()
                        }
                    }
                }
                .id(displayedTab)
                .transition(.opacity.combined(with: .move(edge: .trailing)))
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
            }
            .animation(.easeInOut(duration: 0.22), value: displayedTab)
            .animation(.easeInOut(duration: 0.22), value: tabs)
            .onChange(of: selectedTab) { _ in
                sessionSelectMode = false
                selectedSessionIDs.removeAll()
            }
        }
        .background(detailBackground(for: chargedDevice))
        .onAppear {
            ensureSelectedTabExists(for: chargedDevice)
        }
        .onChange(of: chargedDevice.isCharger) { _ in
            ensureSelectedTabExists(for: chargedDevice)
        }
    }

    @ViewBuilder
    private func tabContent(_ tab: DetailTab, chargedDevice: ChargedDeviceSummary) -> some View {
        VStack(spacing: 18) {
            switch tab {
            case .overview:
                overviewTab(chargedDevice)
            case .standby:
                standbyTab(chargedDevice)
            case .sessions:
                sessionsTab(chargedDevice)
            case .trends:
                trendsTab(chargedDevice)
            case .settings:
                settingsTab(chargedDevice)
            }
        }
    }

    @ViewBuilder
    private func overviewTab(_ chargedDevice: ChargedDeviceSummary) -> some View {
        headerCard(chargedDevice)
        insightsCard(chargedDevice)

        if let activeSession = chargedDevice.activeSession {
            activeSessionSummaryCard(activeSession, chargedDevice: chargedDevice)
        }
    }

    @ViewBuilder
    private func standbyTab(_ chargedDevice: ChargedDeviceSummary) -> some View {
        standbyPowerCard(chargedDevice)
    }

    @ViewBuilder
    private func sessionsTab(_ chargedDevice: ChargedDeviceSummary) -> some View {
        if let activeSession = chargedDevice.activeSession {
            activeSessionSummaryCard(activeSession, chargedDevice: chargedDevice)
        }

        let sessions = closedSessions(for: chargedDevice)
        if !sessions.isEmpty {
            sessionListCard(sessions, chargedDevice: chargedDevice)
        } else if chargedDevice.activeSession == nil {
            emptyStateCard(
                title: "No Sessions",
                message: "Charging sessions will appear here after this device is used in a recording.",
                tint: .teal
            )
        }
    }

    @ViewBuilder
    private func trendsTab(_ chargedDevice: ChargedDeviceSummary) -> some View {
        if !chargedDevice.capacityHistory.isEmpty {
            capacityEvolutionCard(chargedDevice)
        }

        if !chargedDevice.typicalCurve.isEmpty {
            typicalCurveCard(chargedDevice)
        }

        if chargedDevice.capacityHistory.isEmpty && chargedDevice.typicalCurve.isEmpty {
            emptyStateCard(
                title: "Learning Trends",
                message: "Capacity history and charge curves will appear after enough completed sessions are available.",
                tint: .blue
            )
        }
    }

    @ViewBuilder
    private func settingsTab(_ chargedDevice: ChargedDeviceSummary) -> some View {
        settingsCard(chargedDevice)
    }

    @ViewBuilder
    private func sessionsTabLayout(_ chargedDevice: ChargedDeviceSummary) -> some View {
        let allSessions = chargedDevice.sessions.sorted { lhs, rhs in
            let lOpen = lhs.status.isOpen, rOpen = rhs.status.isOpen
            if lOpen != rOpen { return lOpen }
            return lhs.startedAt > rhs.startedAt
        }
        let totalEnergyWh = allSessions.reduce(0.0) { $0 + $1.effectiveOrMeasuredEnergyWh }
        let totalDuration  = allSessions.reduce(0.0) { $0 + max($1.effectiveDuration, 0) }

        VStack(spacing: 0) {
            // Fixed non-scrolling header
            VStack(spacing: 10) {
                sessionsSummaryStrip(
                    count: allSessions.count,
                    totalEnergyWh: totalEnergyWh,
                    totalDuration: totalDuration,
                    hasActive: chargedDevice.activeSession != nil
                )

                if !allSessions.isEmpty {
                    HStack(spacing: 12) {
                        if sessionSelectMode && !selectedSessionIDs.isEmpty {
                            Text("\(selectedSessionIDs.count) selected")
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                                .transition(.opacity.combined(with: .move(edge: .leading)))
                        }
                        Spacer()
                        if sessionSelectMode && !selectedSessionIDs.isEmpty {
                            Button {
                                pendingBatchDeletion = true
                            } label: {
                                Image(systemName: "trash").foregroundColor(.red)
                            }
                            .transition(.opacity.combined(with: .scale))
                        }
                        Button(sessionSelectMode ? "Cancel" : "Select") {
                            withAnimation(.easeInOut(duration: 0.2)) {
                                sessionSelectMode.toggle()
                                if !sessionSelectMode { selectedSessionIDs.removeAll() }
                            }
                        }
                    }
                    .animation(.easeInOut(duration: 0.2), value: sessionSelectMode)
                    .animation(.easeInOut(duration: 0.2), value: selectedSessionIDs.isEmpty)
                }
            }
            .padding()

            // Scrollable session list
            if allSessions.isEmpty {
                emptyStateCard(
                    title: "No Sessions",
                    message: "Charging sessions will appear here after this device is used in a recording.",
                    tint: .teal
                )
                .padding([.horizontal, .bottom])
            } else {
                ScrollView {
                    VStack(spacing: 10) {
                        ForEach(allSessions, id: \.id) { session in
                            sessionListItem(session, chargedDevice: chargedDevice)
                        }
                    }
                    .padding([.horizontal, .bottom])
                }
            }
        }
    }

    private func sessionsSummaryStrip(
        count: Int,
        totalEnergyWh: Double,
        totalDuration: TimeInterval,
        hasActive: Bool
    ) -> some View {
        HStack(spacing: 0) {
            summaryCell(value: "\(count)", label: count == 1 ? "session" : "sessions")
            Divider().frame(height: 30)
            summaryCell(value: "\(totalEnergyWh.format(decimalDigits: 2)) Wh", label: "energy")
            Divider().frame(height: 30)
            summaryCell(value: formatAccumulatedDuration(totalDuration), label: "duration")
            if hasActive {
                Divider().frame(height: 30)
                HStack(spacing: 4) {
                    Circle().fill(Color.green).frame(width: 6, height: 6)
                    Text("Live")
                        .font(.caption2.weight(.semibold))
                        .foregroundColor(.green)
                }
                .frame(maxWidth: .infinity)
            }
        }
        .padding(.vertical, 8)
        .padding(.horizontal, 12)
        .meterCard(tint: .teal, fillOpacity: 0.08, strokeOpacity: 0.14, cornerRadius: 14)
    }

    private func summaryCell(value: String, label: String) -> some View {
        VStack(spacing: 2) {
            Text(value)
                .font(.subheadline.weight(.bold))
                .foregroundColor(.primary)
                .monospacedDigit()
                .lineLimit(1)
                .minimumScaleFactor(0.7)
            Text(label)
                .font(.caption2)
                .foregroundColor(.secondary)
        }
        .frame(maxWidth: .infinity)
    }

    private func formatAccumulatedDuration(_ duration: TimeInterval) -> String {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = duration >= 3600 ? [.hour, .minute] : [.minute, .second]
        formatter.unitsStyle = .abbreviated
        formatter.zeroFormattingBehavior = .dropAll
        return formatter.string(from: duration) ?? "0m"
    }

    private func headerCard(_ chargedDevice: ChargedDeviceSummary) -> some View {
        HStack(alignment: .top, spacing: 18) {
            ChargedDeviceQRCodeView(qrIdentifier: chargedDevice.qrIdentifier, side: 118)

            VStack(alignment: .leading, spacing: 10) {
                ChargedDeviceIdentityLabelView(
                    chargedDevice: chargedDevice,
                    iconPointSize: 22
                )
                .font(.title3.weight(.bold))

                Text(chargedDevice.identityTitle)
                    .font(.subheadline.weight(.semibold))
                    .foregroundColor(.secondary)

                Text(chargedDevice.qrIdentifier)
                    .font(.caption2.monospaced())
                    .foregroundColor(.secondary)
                    .textSelection(.enabled)
            }

            Spacer(minLength: 0)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(18)
        .meterCard(tint: tint(for: chargedDevice), fillOpacity: 0.20, strokeOpacity: 0.26, cornerRadius: 20)
    }

    private func settingsCard(_ chargedDevice: ChargedDeviceSummary) -> some View {
        MeterInfoCardView(title: "Settings", tint: tint(for: chargedDevice)) {
            MeterInfoRowView(
                label: "Kind",
                value: chargedDevice.isCharger ? "Charger" : chargedDevice.deviceClass.title
            )
            MeterInfoRowView(label: "Template", value: chargedDevice.identityTitle)
            MeterInfoRowView(label: "QR ID", value: chargedDevice.qrIdentifier)

            if chargedDevice.supportsInternalSubject {
                MeterInfoRowView(
                    label: "Subject",
                    value: chargedDevice.hasInternalSubject ? "Inside" : "Empty"
                )
            }

            MeterInfoRowView(label: "Created", value: chargedDevice.createdAt.format())
            MeterInfoRowView(label: "Updated", value: chargedDevice.updatedAt.format())

            Divider()

            Button(action: showEditor) {
                Label("Edit \(chargedDevice.isCharger ? "Charger" : "Device")", systemImage: "pencil")
                    .font(.subheadline.weight(.semibold))
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 10)
                    .meterCard(tint: tint(for: chargedDevice), fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            }
            .buttonStyle(.plain)

            Button(role: .destructive, action: showDeleteConfirmation) {
                Label("Delete \(chargedDevice.isCharger ? "Charger" : "Device")", systemImage: "trash")
                    .font(.subheadline.weight(.semibold))
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 10)
                    .meterCard(tint: .red, fillOpacity: 0.10, strokeOpacity: 0.18, cornerRadius: 14)
            }
            .buttonStyle(.plain)
        }
    }

    private func emptyStateCard(title: String, message: String, tint: Color) -> some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(title)
                .font(.headline)
            Text(message)
                .font(.footnote)
                .foregroundColor(.secondary)
                .fixedSize(horizontal: false, vertical: true)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(18)
        .meterCard(tint: tint, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 18)
    }

    private func insightsCard(_ chargedDevice: ChargedDeviceSummary) -> some View {
        MeterInfoCardView(title: "Insights", tint: tint(for: chargedDevice)) {
            if chargedDevice.isCharger {
                chargerInsights(chargedDevice)
            } else {
                deviceInsights(chargedDevice)
            }

            if let notes = chargedDevice.notes, !notes.isEmpty {
                Divider()
                Text(notes)
                    .font(.footnote)
                    .foregroundColor(.secondary)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
        }
    }

    @ViewBuilder
    private func deviceInsights(_ chargedDevice: ChargedDeviceSummary) -> some View {
        if chargedDevice.hasMultipleChargingStateModes {
            MeterInfoRowView(
                label: "Charge Modes",
                value: chargedDevice.chargingStateAvailability.title
            )
        }
        if chargedDevice.hasMultipleChargingTransports {
            MeterInfoRowView(
                label: "Charging Support",
                value: chargedDevice.supportedChargingModes.map(\.title).joined(separator: " + ")
            )
        }
        if chargedDevice.showsWirelessProfileDetails {
            MeterInfoRowView(
                label: "Wireless Profile",
                value: chargedDevice.wirelessChargingProfile.title
            )
        }

        ForEach(completionSessionKinds(for: chargedDevice), id: \.rawValue) { sessionKind in
            MeterInfoRowView(
                label: completionCurrentLabel(for: chargedDevice, sessionKind: sessionKind),
                value: completionCurrentDescription(for: chargedDevice, sessionKind: sessionKind)
            )
        }
        MeterInfoRowView(
            label: "Estimated Capacity",
            value: chargedDevice.estimatedBatteryCapacityWh.map { "\($0.format(decimalDigits: 2)) Wh" } ?? "Not enough data"
        )
        if let wiredCapacity = chargedDevice.wiredEstimatedBatteryCapacityWh {
            if chargedDevice.hasMultipleChargingTransports {
                MeterInfoRowView(
                    label: "Wired Capacity",
                    value: "\(wiredCapacity.format(decimalDigits: 2)) Wh"
                )
            }
        }
        if let wirelessCapacity = chargedDevice.wirelessEstimatedBatteryCapacityWh {
            if chargedDevice.hasMultipleChargingTransports {
                MeterInfoRowView(
                    label: "Wireless Capacity",
                    value: "\(wirelessCapacity.format(decimalDigits: 2)) Wh"
                )
            }
        }
        if let wirelessEfficiencyFactor = chargedDevice.wirelessChargerEfficiencyFactor,
           chargedDevice.showsWirelessProfileDetails {
            MeterInfoRowView(
                label: "Wireless Efficiency",
                value: "\(Int((wirelessEfficiencyFactor * 100).rounded()))%"
            )
        }
        MeterInfoRowView(
            label: "Charge Sessions",
            value: "\(chargedDevice.sessionCount)"
        )
    }

    @ViewBuilder
    private func chargerInsights(_ chargedDevice: ChargedDeviceSummary) -> some View {
        if let chargerType = chargedDevice.chargerType {
            MeterInfoRowView(
                label: "Type",
                value: chargerType.title
            )
        }
        if !chargedDevice.chargerObservedVoltageSelections.isEmpty {
            MeterInfoRowView(
                label: "Observed Voltages",
                value: chargedDevice.chargerObservedVoltageSelections
                    .map { "\($0.format(decimalDigits: 1)) V" }
                    .joined(separator: ", ")
            )
        }
        if let chargerIdleCurrentAmps = chargedDevice.chargerIdleCurrentAmps {
            MeterInfoRowView(
                label: "Idle Current",
                value: "\(chargerIdleCurrentAmps.format(decimalDigits: 2)) A"
            )
        }
        if let chargerEfficiencyFactor = chargedDevice.chargerEfficiencyFactor {
            MeterInfoRowView(
                label: "Efficiency",
                value: "\(Int((chargerEfficiencyFactor * 100).rounded()))%"
            )
        }
        if let chargerMaximumPowerWatts = chargedDevice.chargerMaximumPowerWatts {
            MeterInfoRowView(
                label: "Max Power",
                value: "\(chargerMaximumPowerWatts.format(decimalDigits: 2)) W"
            )
        }
        if let latestStandbyPowerMeasurement = chargedDevice.latestStandbyPowerMeasurement {
            MeterInfoRowView(
                label: "Standby Power",
                value: "\(latestStandbyPowerMeasurement.averagePowerWatts.format(decimalDigits: 3)) W"
            )
            MeterInfoRowView(
                label: "Standby Projection",
                value: standbyEnergyLabel(latestStandbyPowerMeasurement.projectedYearlyEnergyWh) + " / year"
            )
        }
        MeterInfoRowView(
            label: "Wireless Sessions",
            value: "\(chargedDevice.sessionCount)"
        )

    }

    private func standbyPowerCard(_ chargedDevice: ChargedDeviceSummary) -> some View {
        let latestMeasurement = chargedDevice.latestStandbyPowerMeasurement

        return MeterInfoCardView(
            title: "Standby Power",
            tint: .orange
        ) {
            if standbyMeasurementMeters.isEmpty {
                Text("Connect a meter first. Standby measurement is launched from a live meter feed, so only currently available meters can be selected here.")
                    .font(.footnote)
                    .foregroundColor(.secondary)
                    .frame(maxWidth: .infinity, alignment: .leading)
            } else {
                NavigationLink(
                    destination: ChargerStandbyPowerWizardView(
                        preferredChargerID: chargedDevice.id,
                        locksChargerSelection: true
                    )
                ) {
                    Label("New Measurement", systemImage: "plus.circle.fill")
                        .font(.subheadline.weight(.semibold))
                        .foregroundColor(.orange)
                }
                .buttonStyle(.plain)
            }

            if let latestMeasurement {
                Divider()

                NavigationLink(
                    destination: ChargerStandbyPowerMeasurementDetailView(
                        chargerID: chargedDevice.id,
                        measurementID: latestMeasurement.id
                    )
                ) {
                    VStack(alignment: .leading, spacing: 8) {
                        HStack {
                            Text("Latest Measurement")
                                .font(.subheadline.weight(.semibold))
                                .foregroundColor(.primary)
                            Spacer()
                            Text("\(latestMeasurement.averagePowerWatts.format(decimalDigits: 3)) W")
                                .font(.subheadline.weight(.bold))
                                .foregroundColor(.primary)
                                .monospacedDigit()
                        }

                        Text(
                            "\(latestMeasurement.endedAt.format()) • \(latestMeasurement.sampleCount) samples • \(standbyEnergyLabel(latestMeasurement.projectedYearlyEnergyWh)) / year"
                        )
                        .font(.caption)
                        .foregroundColor(.secondary)
                    }
                }
                .buttonStyle(.plain)
            }

            if chargedDevice.standbyPowerMeasurements.isEmpty == false {
                Divider()

                NavigationLink(
                    destination: ChargerStandbyPowerMeasurementsView(chargerID: chargedDevice.id)
                ) {
                    Label("View Saved Measurements", systemImage: "clock.arrow.trianglehead.counterclockwise.rotate.90")
                        .font(.subheadline.weight(.semibold))
                        .foregroundColor(.blue)
                }
                .buttonStyle(.plain)
            }
        }
    }

    private func activeSessionSummaryCard(
        _ activeSession: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        NavigationLink(
            destination: ChargeSessionDetailView(
                chargedDeviceID: chargedDevice.id,
                sessionID: activeSession.id
            )
        ) {
            VStack(alignment: .leading, spacing: 14) {
                HStack(alignment: .firstTextBaseline) {
                    VStack(alignment: .leading, spacing: 4) {
                        Text("Current Session")
                            .font(.headline)
                            .foregroundColor(.primary)
                        Text(activeSession.status.title)
                            .font(.caption.weight(.semibold))
                            .foregroundColor(statusTint(for: activeSession))
                    }

                    Spacer()

                    Image(systemName: "chevron.right")
                        .font(.caption.weight(.semibold))
                        .foregroundColor(.secondary)
                }

                LazyVGrid(columns: activeSessionSummaryColumns, spacing: 8) {
                    activeSessionMetricCell(
                        label: "Energy",
                        value: "\(activeSession.effectiveOrMeasuredEnergyWh.format(decimalDigits: 2)) Wh",
                        tint: .teal
                    )
                    activeSessionMetricCell(
                        label: "Duration",
                        value: sessionDurationText(activeSession),
                        tint: .orange
                    )
                    if let maximumObservedPowerWatts = activeSession.maximumObservedPowerWatts {
                        activeSessionMetricCell(
                            label: "Max Power",
                            value: "\(maximumObservedPowerWatts.format(decimalDigits: 2)) W",
                            tint: .blue
                        )
                    }
                    if let batteryPrediction = chargedDevice.batteryLevelPrediction(for: activeSession) {
                        activeSessionMetricCell(
                            label: "Battery",
                            value: "\(batteryPrediction.predictedPercent.format(decimalDigits: 0))%",
                            tint: .green
                        )
                    } else if let targetBatteryPercent = activeSession.targetBatteryPercent {
                        activeSessionMetricCell(
                            label: "Target",
                            value: "\(targetBatteryPercent.format(decimalDigits: 0))%",
                            tint: .indigo
                        )
                    }
                }

                Text("Started \(activeSession.startedAt.format())")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
        }
        .buttonStyle(.plain)
        .padding(18)
        .meterCard(tint: statusTint(for: activeSession), fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 18)
    }

    private var activeSessionSummaryColumns: [GridItem] {
        [
            GridItem(.flexible(minimum: 92), spacing: 8),
            GridItem(.flexible(minimum: 92), spacing: 8)
        ]
    }

    private func activeSessionMetricCell(label: String, value: String, tint: Color) -> some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(label)
                .font(.caption2)
                .foregroundColor(.secondary)
            Text(value)
                .font(.footnote.weight(.semibold))
                .foregroundColor(.primary)
                .monospacedDigit()
                .lineLimit(1)
                .minimumScaleFactor(0.8)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(10)
        .meterCard(tint: tint, fillOpacity: 0.08, strokeOpacity: 0.12, cornerRadius: 12)
    }

    private func capacityEvolutionCard(_ chargedDevice: ChargedDeviceSummary) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Capacity Evolution")
                .font(.headline)

            ForEach(chargedDevice.capacityHistory.suffix(6)) { point in
                HStack {
                    Text(point.timestamp.format())
                        .font(.caption)
                        .foregroundColor(.secondary)
                    Spacer()
                    if chargedDevice.shouldShowChargingTransport(point.chargingTransportMode) {
                        Text(point.chargingTransportMode.title)
                            .font(.caption2)
                            .foregroundColor(.secondary)
                        Text("•")
                            .foregroundColor(.secondary)
                    }
                    Text("\(point.capacityWh.format(decimalDigits: 2)) Wh")
                        .font(.footnote.weight(.semibold))
                }
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(18)
        .meterCard(tint: .orange, fillOpacity: 0.14, strokeOpacity: 0.20, cornerRadius: 18)
    }

    private func typicalCurveCard(_ chargedDevice: ChargedDeviceSummary) -> some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Typical Charge Curve")
                .font(.headline)

            ForEach(chargedDevice.typicalCurve) { point in
                HStack {
                    Text("\(point.percentBin)%")
                        .font(.footnote.weight(.semibold))
                    Spacer()
                    Text("\(point.averageEnergyWh.format(decimalDigits: 2)) Wh")
                        .font(.caption.weight(.semibold))
                    Text("•")
                        .foregroundColor(.secondary)
                    Text("\(point.sampleCount) sample\(point.sampleCount == 1 ? "" : "s")")
                        .font(.caption2)
                        .foregroundColor(.secondary)
                }
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(18)
        .meterCard(tint: .blue, fillOpacity: 0.14, strokeOpacity: 0.20, cornerRadius: 18)
    }

    private func sessionListCard(
        _ sessions: [ChargeSessionSummary],
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        let totalEnergyWh = sessions.reduce(0) { $0 + $1.effectiveOrMeasuredEnergyWh }
        let completedCount = sessions.filter { $0.status == .completed }.count
        let sortedSessions = sessions.sorted { $0.startedAt > $1.startedAt }

        return VStack(alignment: .leading, spacing: 14) {
            MeterInfoCardView(title: "Closed Sessions", tint: .teal) {
                MeterInfoRowView(label: "Sessions", value: "\(sessions.count)")
                MeterInfoRowView(label: "Completed", value: "\(completedCount)")
                MeterInfoRowView(label: "Total Energy", value: "\(totalEnergyWh.format(decimalDigits: 2)) Wh")
            }

            HStack(spacing: 12) {
                if sessionSelectMode && !selectedSessionIDs.isEmpty {
                    Text("\(selectedSessionIDs.count) selected")
                        .font(.subheadline)
                        .foregroundColor(.secondary)
                        .transition(.opacity.combined(with: .move(edge: .leading)))
                }
                Spacer()
                if sessionSelectMode && !selectedSessionIDs.isEmpty {
                    Button {
                        pendingBatchDeletion = true
                    } label: {
                        Image(systemName: "trash")
                            .foregroundColor(.red)
                    }
                    .transition(.opacity.combined(with: .scale))
                }
                Button(sessionSelectMode ? "Cancel" : "Select") {
                    withAnimation(.easeInOut(duration: 0.2)) {
                        sessionSelectMode.toggle()
                        if !sessionSelectMode { selectedSessionIDs.removeAll() }
                    }
                }
            }
            .animation(.easeInOut(duration: 0.2), value: sessionSelectMode)
            .animation(.easeInOut(duration: 0.2), value: selectedSessionIDs.isEmpty)

            VStack(spacing: 10) {
                ForEach(sortedSessions, id: \.id) { session in
                    sessionListItem(session, chargedDevice: chargedDevice)
                }
            }
        }
    }

    private func sessionListItem(
        _ session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        let sessionTint = statusTint(for: session)
        let isOpen = session.status.isOpen
        let isSelected = selectedSessionIDs.contains(session.id)

        return Group {
            if sessionSelectMode && !isOpen {
                Button {
                    withAnimation(.easeInOut(duration: 0.15)) {
                        if isSelected { selectedSessionIDs.remove(session.id) }
                        else          { selectedSessionIDs.insert(session.id) }
                    }
                } label: {
                    sessionRowContent(session, sessionTint: sessionTint, isOpen: isOpen, isSelected: isSelected)
                }
                .buttonStyle(.plain)
            } else {
                NavigationLink(
                    destination: ChargeSessionDetailView(
                        chargedDeviceID: chargedDevice.id,
                        sessionID: session.id
                    )
                ) {
                    sessionRowContent(session, sessionTint: sessionTint, isOpen: isOpen, isSelected: false)
                }
                .buttonStyle(.plain)
            }
        }
    }

    private func sessionRowContent(
        _ session: ChargeSessionSummary,
        sessionTint: Color,
        isOpen: Bool,
        isSelected: Bool
    ) -> some View {
        VStack(alignment: .leading, spacing: 10) {
            HStack(alignment: .firstTextBaseline, spacing: 10) {
                if sessionSelectMode {
                    Group {
                        if isOpen {
                            Image(systemName: "minus.circle")
                                .foregroundColor(.secondary.opacity(0.35))
                        } else {
                            Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
                                .foregroundColor(isSelected ? .teal : .secondary)
                        }
                    }
                    .font(.body)
                    .transition(.opacity)
                }

                VStack(alignment: .leading, spacing: 2) {
                    Text(session.startedAt.format())
                        .font(.subheadline.weight(.semibold))
                    Text(session.status.title)
                        .font(.caption2)
                        .foregroundColor(sessionTint)
                }

                Spacer()

                VStack(alignment: .trailing, spacing: 2) {
                    Text("\(session.effectiveOrMeasuredEnergyWh.format(decimalDigits: 2)) Wh")
                        .font(.subheadline.weight(.semibold))
                        .foregroundColor(.primary)
                    Text(sessionDurationText(session))
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
            }

            Divider()

            HStack(spacing: 8) {
                if let batteryDelta = session.batteryDeltaPercent {
                    Label("\(batteryDelta >= 0 ? "+" : "")\(Int(batteryDelta.rounded()))% charged", systemImage: "battery.100percent")
                        .font(.caption2)
                        .foregroundColor(.secondary)
                }

                if let capacityWh = session.capacityEstimateWh {
                    Text("est. \(capacityWh.format(decimalDigits: 1)) Wh")
                        .font(.caption2)
                        .foregroundColor(.secondary)
                }

                Spacer()

                if !session.displayedAggregatedSamples.isEmpty {
                    Label("\(session.displayedAggregatedSamples.count) points", systemImage: "chart.xyaxis.line")
                        .font(.caption2)
                        .foregroundColor(.secondary)
                }
            }
        }
        .padding(12)
        .meterCard(
            tint: sessionTint,
            fillOpacity: isSelected ? 0.16 : (isOpen ? 0.14 : 0.08),
            strokeOpacity: isSelected ? 0.22 : (isOpen ? 0.30 : 0.14),
            cornerRadius: 14
        )
    }

    private func closedSessions(for chargedDevice: ChargedDeviceSummary) -> [ChargeSessionSummary] {
        chargedDevice.sessions.filter { !$0.status.isOpen }
    }

    private func availableTabs(for chargedDevice: ChargedDeviceSummary) -> [DetailTab] {
        if chargedDevice.isCharger {
            return [.overview, .standby, .settings]
        }
        return [.overview, .sessions, .trends, .settings]
    }

    private func displayedTab(from tabs: [DetailTab]) -> DetailTab {
        if tabs.contains(selectedTab) {
            return selectedTab
        }
        return tabs.first ?? .overview
    }

    private func ensureSelectedTabExists(for chargedDevice: ChargedDeviceSummary) {
        let tabs = availableTabs(for: chargedDevice)
        if !tabs.contains(selectedTab) {
            selectedTab = tabs.first ?? .overview
        }
    }

    private func title(for tab: DetailTab) -> String {
        switch tab {
        case .overview:
            return "Overview"
        case .standby:
            return "Standby"
        case .sessions:
            return "Sessions"
        case .trends:
            return "Trends"
        case .settings:
            return "Settings"
        }
    }

    private func systemImage(for tab: DetailTab) -> String {
        switch tab {
        case .overview:
            return "house.fill"
        case .standby:
            return "bolt.badge.clock"
        case .sessions:
            return "clock.arrow.trianglehead.counterclockwise.rotate.90"
        case .trends:
            return "chart.xyaxis.line"
        case .settings:
            return "gearshape.fill"
        }
    }

    private func detailBackground(for chargedDevice: ChargedDeviceSummary) -> some View {
        LinearGradient(
            colors: [tint(for: chargedDevice).opacity(0.18), Color.clear],
            startPoint: .topLeading,
            endPoint: .bottomTrailing
        )
        .ignoresSafeArea()
    }

    private func tint(for chargedDevice: ChargedDeviceSummary) -> Color {
        switch chargedDevice.deviceClass {
        case .iphone:
            return .blue
        case .watch:
            return .green
        case .powerbank:
            return .orange
        case .charger:
            return .pink
        case .other:
            return .secondary
        }
    }

    private func statusTint(for session: ChargeSessionSummary) -> Color {
        switch session.status {
        case .active:
            return .green
        case .paused:
            return .orange
        case .completed:
            return .teal
        case .abandoned:
            return .secondary
        }
    }

    private func sessionDurationText(_ session: ChargeSessionSummary) -> String {
        let formatter = DateComponentsFormatter()
        let effectiveDuration = max(session.effectiveDuration, 0)
        formatter.allowedUnits = effectiveDuration >= 3600 ? [.hour, .minute] : [.minute, .second]
        formatter.unitsStyle = .abbreviated
        formatter.zeroFormattingBehavior = .dropAll
        return formatter.string(from: effectiveDuration) ?? "0m"
    }

    private func standbyEnergyLabel(_ wattHours: Double) -> String {
        if wattHours >= 1000 {
            return "\((wattHours / 1000).format(decimalDigits: 3)) kWh"
        }
        return "\(wattHours.format(decimalDigits: 2)) Wh"
    }

    private var standbyMeasurementMeters: [AppData.MeterSummary] {
        appData.meterSummaries.filter { $0.meter != nil }
    }

    private func completionCurrentDescription(
        for chargedDevice: ChargedDeviceSummary,
        sessionKind: ChargeSessionKind
    ) -> String {
        if let configuredCurrent = chargedDevice.configuredCompletionCurrentAmps(for: sessionKind) {
            if let learnedCurrent = chargedDevice.learnedCompletionCurrentAmps(for: sessionKind),
               abs(configuredCurrent - learnedCurrent) >= 0.01 {
                return "\(configuredCurrent.format(decimalDigits: 2)) A configured • \(learnedCurrent.format(decimalDigits: 2)) A learned"
            }
            return "\(configuredCurrent.format(decimalDigits: 2)) A configured"
        }

        if let learnedCurrent = chargedDevice.learnedCompletionCurrentAmps(for: sessionKind) {
            return "\(learnedCurrent.format(decimalDigits: 2)) A learned"
        }

        return "Learning"
    }

    private func completionCurrentLabel(
        for chargedDevice: ChargedDeviceSummary,
        sessionKind: ChargeSessionKind
    ) -> String {
        let showsTransport = chargedDevice.shouldShowChargingTransport(sessionKind.chargingTransportMode)
        let showsState = chargedDevice.shouldShowChargingStateMode(sessionKind.chargingStateMode)

        switch (showsTransport, showsState) {
        case (true, true):
            return "\(sessionKind.shortTitle) Stop Current"
        case (true, false):
            return "\(sessionKind.chargingTransportMode.title) Stop Current"
        case (false, true):
            return "\(sessionKind.chargingStateMode.title) Stop Current"
        case (false, false):
            return "Stop Current"
        }
    }

    private func completionSessionKinds(for chargedDevice: ChargedDeviceSummary) -> [ChargeSessionKind] {
        chargedDevice.supportedChargingModes.flatMap { chargingTransportMode in
            chargedDevice.supportedChargingStateModes.map { chargingStateMode in
                ChargeSessionKind(
                    chargingTransportMode: chargingTransportMode,
                    chargingStateMode: chargingStateMode
                )
            }
        }
    }

    private func deleteSelectedSessions() {
        for id in selectedSessionIDs {
            _ = appData.deleteChargeSession(sessionID: id)
        }
        selectedSessionIDs.removeAll()
        sessionSelectMode = false
    }

    private func showEditor() {
        editorVisibility = true
    }

    private func showDeleteConfirmation() {
        deleteConfirmationVisibility = true
    }

    private var deletionTitle: String {
        appData.chargedDeviceSummary(id: chargedDeviceID)?.isCharger == true ? "charger" : "device"
    }

    private var deletionMessage: String {
        if appData.chargedDeviceSummary(id: chargedDeviceID)?.isCharger == true {
            return "This removes the charger from the library and unlinks it from wireless sessions that used it."
        }
        return "This removes the device and its stored charging history from the library."
    }

}