1 contributor
2234 lines | 88.032kb
//
//  ChargeSessionDetailView.swift
//  USB Meter
//
//  Created by Codex on 22/04/2026.
//

import SwiftUI

enum ChargeSessionDetailPresentation {
    case navigation
    case embedded
}

struct ChargeSessionDetailView: View {
    private enum FinalCheckpoint: Hashable {
        case full
        case skip
        case custom

        var label: String {
            switch self {
            case .full:   return "Full"
            case .skip:   return "Skip"
            case .custom: return "Other %"
            }
        }

        var icon: String {
            switch self {
            case .full:   return "battery.100percent"
            case .skip:   return "minus.circle"
            case .custom: return "pencil"
            }
        }
    }

    private struct BatteryPercentCandidate {
        let timestamp: Date
        let percent: Double
        let isCheckpoint: Bool
    }

    @EnvironmentObject private var appData: AppData

    let chargedDeviceID: UUID
    let sessionID: UUID
    let monitoringMeter: Meter?
    let presentation: ChargeSessionDetailPresentation

    @State private var pendingCheckpointDeletion: ChargeCheckpointSummary?
    @State private var pendingSessionDeletion: ChargeSessionSummary?
    @State private var pendingSessionStopRequest: ChargeSessionStopRequest?
    @State private var pendingTrimCommitSession: ChargeSessionSummary?
    @State private var detectedTrimWindow: ChargingWindowDetector.DetectedWindow?
    @State private var trimBannerDismissedForSessionID: UUID?
    @State private var showingInlineTargetEditor = false
    @State private var draftTargetText = ""
    @State private var showingStopConfirm = false
    @State private var finalCheckpointMode: FinalCheckpoint = .skip
    @State private var isBatteryCardExpanded = false
    @State private var finalCheckpointText = ""
    @State private var stopFailureMessage: String?

    init(
        chargedDeviceID: UUID,
        sessionID: UUID,
        monitoringMeter: Meter? = nil,
        presentation: ChargeSessionDetailPresentation = .navigation
    ) {
        self.chargedDeviceID = chargedDeviceID
        self.sessionID = sessionID
        self.monitoringMeter = monitoringMeter
        self.presentation = presentation
    }

    private var chargedDevice: ChargedDeviceSummary? {
        appData.chargedDeviceSummary(id: chargedDeviceID)
    }

    private var session: ChargeSessionSummary? {
        chargedDevice?.sessions.first(where: { $0.id == sessionID })
    }

    private var liveMonitoringMeter: Meter? {
        guard let session,
              session.status.isOpen,
              let meterMACAddress = session.meterMACAddress else {
            return nil
        }

        if let monitoringMeter,
           monitoringMeter.btSerial.macAddress.description == meterMACAddress {
            return monitoringMeter
        }

        return appData.meters.values.first {
            $0.btSerial.macAddress.description == meterMACAddress
        }
    }

    private var hasMonitoringControls: Bool {
        session?.status.isOpen == true && liveMonitoringMeter != nil
    }

    private var shouldShowTrimBanner: Bool {
        guard hasMonitoringControls,
              let session,
              session.isTrimmed == false,
              trimBannerDismissedForSessionID != session.id,
              let detectedTrimWindow else {
            return false
        }
        return detectedTrimWindow.trimRatio > ChargingWindowDetector.significantTrimThreshold
    }

    var body: some View {
        Group {
            if let chargedDevice, let session {
                content(chargedDevice: chargedDevice, session: session)
            } else {
                unavailableState
            }
        }
        .sheet(item: $pendingSessionStopRequest) { request in
            ChargeSessionCompletionSheetView(
                sessionID: request.sessionID,
                title: request.title,
                confirmTitle: request.confirmTitle,
                explanation: request.explanation,
                monitoringMeter: liveMonitoringMeter,
                appliesTrim: request.appliesTrim,
                trimStart: request.trimStart,
                trimEnd: request.trimEnd
            )
            .environmentObject(appData)
        }
        .alert(item: $pendingCheckpointDeletion) { checkpoint in
            Alert(
                title: Text("Delete Battery Checkpoint"),
                message: Text("Remove the checkpoint at \(checkpoint.timestamp.format()) with \(checkpoint.batteryPercent.format(decimalDigits: 0))% from this session?"),
                primaryButton: .destructive(Text("Delete")) {
                    _ = appData.deleteBatteryCheckpoint(
                        checkpointID: checkpoint.id,
                        for: checkpoint.sessionID
                    )
                },
                secondaryButton: .cancel()
            )
        }
        .alert(item: $pendingSessionDeletion) { session in
            Alert(
                title: Text("Delete Session?"),
                message: Text("Deleting this charging session also recalculates capacity and every derived metric that used it."),
                primaryButton: .destructive(Text("Delete")) {
                    _ = appData.deleteChargeSession(sessionID: session.id)
                },
                secondaryButton: .cancel()
            )
        }
        .alert(item: $pendingTrimCommitSession) { session in
            Alert(
                title: Text("Save Trim Permanently?"),
                message: Text("Samples and checkpoints outside \(session.effectiveTrimStart.format()) - \(session.effectiveTrimEnd.format()) will be deleted. Reset Trim will no longer restore them."),
                primaryButton: .destructive(Text("Save Trim")) {
                    _ = appData.commitSessionTrim(sessionID: session.id)
                },
                secondaryButton: .cancel()
            )
        }
        .onAppear {
            syncMonitoringRestore()
            runTrimDetection()
        }
        .onChange(of: session?.id) { _ in
            pendingSessionStopRequest = nil
            pendingTrimCommitSession = nil
            detectedTrimWindow = nil
            trimBannerDismissedForSessionID = nil
            showingInlineTargetEditor = false
            draftTargetText = ""
            showingStopConfirm = false
            finalCheckpointMode = .skip
            isBatteryCardExpanded = false
            finalCheckpointText = ""
            stopFailureMessage = nil
            syncMonitoringRestore()
            runTrimDetection()
        }
        .onChange(of: session?.aggregatedSamples.count) { _ in
            syncMonitoringRestore()
            runTrimDetection()
        }
        .onChange(of: session?.checkpoints.count) { _ in
            syncMonitoringRestore()
        }
        .onChange(of: finalCheckpointMode) { _ in
            stopFailureMessage = nil
        }
        .onChange(of: finalCheckpointText) { _ in
            stopFailureMessage = nil
        }
    }

    private func content(
        chargedDevice: ChargedDeviceSummary,
        session: ChargeSessionSummary
    ) -> some View {
        ScrollView {
            VStack(spacing: 16) {
                if hasMonitoringControls {
                    monitoringSessionCard(session, chargedDevice: chargedDevice)

                    if shouldShowTrimBanner {
                        trimDetectionBanner(session)
                    }

                    if shouldShowSessionChart(session) {
                        chartCard(session, chargedDevice: chargedDevice)
                    }
                } else {
                    overviewCard(session, chargedDevice: chargedDevice)
                    batteryCard(session, chargedDevice: chargedDevice)

                    if shouldShowSessionChart(session) {
                        chartCard(session, chargedDevice: chargedDevice)
                    }

                    if session.status.isOpen {
                        followerNoticeCard(session)
                    }
                }
            }
            .padding(presentation == .embedded ? 16 : 20)
        }
        .background(
            LinearGradient(
                colors: [statusTint(for: session).opacity(0.14), Color.clear],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
            .ignoresSafeArea()
        )
        .navigationTitle(session.status.isOpen ? "Current Session" : "Session Details")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItemGroup(placement: .primaryAction) {
                if session.status.isOpen == false {
                    Button(role: .destructive) {
                        pendingSessionDeletion = session
                    } label: {
                        Image(systemName: "trash")
                    }
                    .help("Delete session")
                }
            }
        }
    }

    private var unavailableState: some View {
        VStack(spacing: 12) {
            Image(systemName: "bolt.slash")
                .font(.title2)
                .foregroundColor(.secondary)
            Text("This session is no longer available.")
                .font(.headline)
            Text("It may have been deleted or synced from another device.")
                .font(.footnote)
                .foregroundColor(.secondary)
                .multilineTextAlignment(.center)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding(24)
        .navigationTitle("Session")
        .navigationBarTitleDisplayMode(.inline)
    }

    private func monitoringSessionCard(
        _ session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        let displayedEnergyWh = displayedSessionEnergyWh(for: session)
        let batteryPrediction = chargedDevice.batteryLevelPrediction(
            for: session,
            effectiveEnergyWhOverride: displayedEnergyWh
        )

        return VStack(alignment: .leading, spacing: 14) {
            HStack {
                ChargedDeviceIdentityLabelView(chargedDevice: chargedDevice, iconPointSize: 16)
                    .font(.headline)

                Spacer()

                Text(session.status.title)
                    .font(.caption.weight(.bold))
                    .foregroundColor(monitoringStatusColor(for: session))
                    .padding(.horizontal, 8)
                    .padding(.vertical, 4)
                    .meterCard(tint: monitoringStatusColor(for: session), fillOpacity: 0.18, strokeOpacity: 0.24, cornerRadius: 999)
            }

            if let batteryPrediction {
                batteryGaugeSection(
                    prediction: batteryPrediction,
                    session: session,
                    displayedEnergyWh: displayedEnergyWh
                )
            }

            sessionMetricsGrid(
                session: session,
                chargedDevice: chargedDevice,
                displayedEnergyWh: displayedEnergyWh,
                hasPrediction: batteryPrediction != nil
            )

            if session.stopThresholdAmps > 0 {
                Text("Stop threshold: \(session.stopThresholdAmps.format(decimalDigits: 2)) A")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            if let sessionWarning = sessionWarning(for: session) {
                Label(sessionWarning, systemImage: "exclamationmark.triangle")
                    .font(.caption)
                    .foregroundColor(.orange)
            }

            if session.isPaused {
                Label(
                    "Paused \(session.pausedAt?.format() ?? session.lastObservedAt.format()). Auto-stops after 10 min.",
                    systemImage: "pause.circle"
                )
                .font(.caption)
                .foregroundColor(.secondary)
            }

            if session.requiresCompletionConfirmation && !showingStopConfirm {
                completionConfirmationCard(session)
            }

            BatteryCheckpointSectionView(
                sessionID: session.id,
                checkpoints: session.checkpoints,
                message: "Checkpoints are used for capacity estimation and the typical charge curve.",
                canAddCheckpoint: appData.canAddBatteryCheckpoint(to: session.id),
                canDeleteCheckpoint: true,
                requirementMessage: appData.batteryCheckpointCaptureRequirementMessage(for: session.id),
                effectiveEnergyWhOverride: displayedEnergyWh,
                onDelete: { checkpoint in
                    pendingCheckpointDeletion = checkpoint
                }
            )

            targetSectionView(
                session: session,
                predictedPercent: batteryPrediction?.predictedPercent
            )

            if showingStopConfirm {
                stopConfirmPanel(
                    session: session,
                    displayedEnergyWh: displayedEnergyWh
                )
            } else {
                monitoringActionRow(session)
            }
        }
        .padding(18)
        .meterCard(tint: .green, fillOpacity: 0.14, strokeOpacity: 0.20)
    }

    private func overviewCard(
        _ session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        MeterInfoCardView(
            title: session.status.isOpen ? "Open Session" : "Overview",
            tint: statusTint(for: session),
            isCollapsible: true,
            initiallyExpanded: false,
            trailingActions: {
                HStack(spacing: 4) {
                    Text(session.startedAt, style: .time)
                        .font(.caption2)
                        .foregroundColor(.secondary)
                        .monospacedDigit()
                    Text("·")
                        .font(.caption2)
                        .foregroundColor(.secondary)
                    Text(sessionDurationText(session))
                        .font(.caption2.weight(.semibold))
                        .foregroundColor(.secondary)
                        .monospacedDigit()
                }
            }
        ) {
            VStack(alignment: .leading, spacing: 10) {
                MeterInfoRowView(label: "Device", value: chargedDevice.name)

                Divider()

                HStack(alignment: .top, spacing: 12) {
                    overviewStatCell(label: "Started", value: session.startedAt.format())
                    if let endedAt = session.endedAt {
                        overviewStatCell(label: "Ended", value: endedAt.format())
                    }
                }

                HStack(alignment: .top, spacing: 12) {
                    overviewStatCell(label: "Duration", value: sessionDurationText(session))
                    overviewStatCell(label: "Status", value: session.status.title)
                }

                Divider()

                HStack(alignment: .top, spacing: 12) {
                    overviewStatCell(label: "Charging Type", value: session.chargingTransportMode.title)
                    overviewStatCell(label: "Charging Mode", value: session.chargingStateMode.title)
                }

                HStack(alignment: .top, spacing: 12) {
                    overviewStatCell(label: "Source", value: session.sourceMode.title)
                    overviewStatCell(label: "Auto Stop", value: autoStopDescription(for: session))
                }

                if session.isTrimmed {
                    Divider()
                    HStack(alignment: .top, spacing: 12) {
                        overviewStatCell(label: "Trim Start", value: session.effectiveTrimStart.format())
                        overviewStatCell(label: "Trim End", value: session.effectiveTrimEnd.format())
                    }
                }

                let meterLabel: String? = session.meterName ?? session.meterMACAddress
                if meterLabel != nil || session.meterModel != nil {
                    Divider()
                    HStack(alignment: .top, spacing: 12) {
                        if let label = meterLabel {
                            overviewStatCell(label: "Meter", value: label)
                        }
                        if let model = session.meterModel {
                            overviewStatCell(label: "Meter Model", value: model)
                        }
                    }
                }

                if session.minimumObservedCurrentAmps != nil
                    || session.maximumObservedCurrentAmps != nil
                    || session.maximumObservedPowerWatts != nil
                    || session.maximumObservedVoltageVolts != nil
                    || (chargedDevice.isCharger && session.selectedSourceVoltageVolts != nil)
                    || session.completionCurrentAmps != nil
                    || session.selectedDataGroup != nil {

                    Divider()

                    if session.minimumObservedCurrentAmps != nil || session.maximumObservedCurrentAmps != nil {
                        HStack(alignment: .top, spacing: 12) {
                            if let v = session.minimumObservedCurrentAmps {
                                overviewStatCell(label: "Min Current", value: "\(v.format(decimalDigits: 2)) A")
                            }
                            if let v = session.maximumObservedCurrentAmps {
                                overviewStatCell(label: "Max Current", value: "\(v.format(decimalDigits: 2)) A")
                            }
                        }
                    }

                    if session.maximumObservedPowerWatts != nil || session.maximumObservedVoltageVolts != nil {
                        HStack(alignment: .top, spacing: 12) {
                            if let v = session.maximumObservedPowerWatts {
                                overviewStatCell(label: "Max Power", value: "\(v.format(decimalDigits: 2)) W")
                            }
                            if let v = session.maximumObservedVoltageVolts {
                                overviewStatCell(label: "Max Voltage", value: "\(v.format(decimalDigits: 2)) V")
                            }
                        }
                    }

                    if session.completionCurrentAmps != nil
                        || (chargedDevice.isCharger && session.selectedSourceVoltageVolts != nil) {
                        HStack(alignment: .top, spacing: 12) {
                            if let v = session.completionCurrentAmps {
                                overviewStatCell(label: "Completion Current", value: "\(v.format(decimalDigits: 2)) A")
                            }
                            if chargedDevice.isCharger, let v = session.selectedSourceVoltageVolts {
                                overviewStatCell(label: "Selected Voltage", value: "\(v.format(decimalDigits: 2)) V")
                            }
                        }
                    }

                    if let dg = session.selectedDataGroup {
                        MeterInfoRowView(label: "Data Group", value: "\(dg)")
                    }
                }
            }
        }
    }

    private func overviewStatCell(label: String, value: String) -> some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(label)
                .font(.caption2)
                .foregroundColor(.secondary)
            Text(value)
                .font(.footnote.weight(.medium))
                .monospacedDigit()
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    private func batteryCard(
        _ session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        let displayedEnergyWh = displayedSessionEnergyWh(for: session)
        let batteryPrediction = chargedDevice.batteryLevelPrediction(
            for: session,
            effectiveEnergyWhOverride: hasMonitoringControls ? displayedEnergyWh : nil
        )

        let startPercent = session.startBatteryPercent
        let endPercent = session.endBatteryPercent ?? batteryPrediction?.predictedPercent
        let isEstimatedEnd = session.endBatteryPercent == nil && batteryPrediction != nil
        let showsPreview = startPercent != nil && endPercent != nil

        return VStack(alignment: .leading, spacing: 0) {

            // Header — always visible, tappable
            HStack(spacing: 8) {
                Text("Battery")
                    .font(.headline)
                Spacer(minLength: 0)
                Image(systemName: "chevron.up")
                    .font(.caption.weight(.semibold))
                    .foregroundColor(.secondary)
                    .rotationEffect(.degrees(isBatteryCardExpanded ? 0 : -180))
                    .animation(.easeInOut(duration: 0.2), value: isBatteryCardExpanded)
            }
            .contentShape(Rectangle())
            .onTapGesture {
                withAnimation(.easeInOut(duration: 0.25)) {
                    isBatteryCardExpanded.toggle()
                }
            }

            // Preview bar — always visible when there is enough data
            if showsPreview, let start = startPercent, let end = endPercent {
                batteryPreviewBar(
                    startPercent: start,
                    endPercent: end,
                    checkpoints: session.checkpoints,
                    isEstimatedEnd: isEstimatedEnd
                )
                .padding(.top, 10)
            }

            // Collapsible detail
            if isBatteryCardExpanded {
                VStack(alignment: .leading, spacing: 10) {

                    // Energy
                    HStack(alignment: .top, spacing: 12) {
                        overviewStatCell(label: "Battery Energy", value: "\(displayedEnergyWh.format(decimalDigits: 3)) Wh")
                        if let capacityEstimateWh = session.capacityEstimateWh {
                            overviewStatCell(label: "Capacity Estimate", value: "\(capacityEstimateWh.format(decimalDigits: 2)) Wh")
                        }
                    }
                    if abs(displayedEnergyWh - session.measuredEnergyWh) > 0.01 {
                        MeterInfoRowView(label: "Measured Energy", value: "\(session.measuredEnergyWh.format(decimalDigits: 3)) Wh")
                    }
                    if let effectiveBatteryEnergyWh = session.effectiveBatteryEnergyWh,
                       abs(effectiveBatteryEnergyWh - session.measuredEnergyWh) > 0.01 {
                        MeterInfoRowView(label: "Effective Battery Energy", value: "\(effectiveBatteryEnergyWh.format(decimalDigits: 3)) Wh")
                    }
                    if let chargerID = session.chargerID,
                       let charger = appData.chargedDeviceSummary(id: chargerID) {
                        MeterInfoRowView(label: chargedDevice.hasMultipleChargingTransports ? "Wireless Charger" : "Charger", value: charger.name)
                    }
                    if let wirelessSessionHint = wirelessSessionHint(for: session) {
                        Text(wirelessSessionHint)
                            .font(.caption2)
                            .foregroundColor(session.shouldWarnAboutLowWirelessEfficiency ? .orange : .secondary)
                    }
                    if let sessionWarning = sessionWarning(for: session) {
                        Label(sessionWarning, systemImage: "exclamationmark.triangle")
                            .font(.caption2)
                            .foregroundColor(.orange)
                    }

                    // Battery percentages
                    if startPercent != nil || session.endBatteryPercent != nil {
                        Divider()
                        HStack(alignment: .top, spacing: 12) {
                            if let v = startPercent {
                                overviewStatCell(
                                    label: "Start Battery",
                                    value: session.startsFromFlatBattery ? "Flat" : "\(v.format(decimalDigits: 0))%"
                                )
                            }
                            if let v = session.endBatteryPercent {
                                overviewStatCell(label: "End Battery", value: "\(v.format(decimalDigits: 0))%")
                            }
                        }
                        if session.batteryDeltaPercent != nil || session.targetBatteryPercent != nil {
                            HStack(alignment: .top, spacing: 12) {
                                if let v = session.batteryDeltaPercent {
                                    overviewStatCell(label: "Battery Delta", value: "\(v.format(decimalDigits: 0))%")
                                }
                                if let v = session.targetBatteryPercent {
                                    overviewStatCell(label: "Target", value: "\(v.format(decimalDigits: 0))%")
                                }
                            }
                        }
                        if let batteryPrediction {
                            HStack(alignment: .top, spacing: 12) {
                                overviewStatCell(label: "Predicted Battery", value: "\(batteryPrediction.predictedPercent.format(decimalDigits: 0))%")
                            }
                            Text(batteryPredictionExplanation(batteryPrediction))
                            .font(.caption2)
                            .foregroundColor(.secondary)
                        }
                    }

                    // Checkpoints
                    Divider()
                    BatteryCheckpointSectionView(
                        sessionID: session.id,
                        checkpoints: session.checkpoints,
                        message: session.status.isOpen
                            ? "The checkpoint is stored on the active charge session and is used for capacity estimation and charge-level prediction."
                            : "These checkpoints were saved with this closed session and feed capacity estimation and charge-level prediction.",
                        canAddCheckpoint: hasMonitoringControls && appData.canAddBatteryCheckpoint(to: session.id),
                        canDeleteCheckpoint: hasMonitoringControls || session.status.isOpen == false,
                        requirementMessage: session.status.isOpen ? appData.batteryCheckpointCaptureRequirementMessage(for: session.id) : nil,
                        effectiveEnergyWhOverride: hasMonitoringControls ? displayedEnergyWh : nil,
                        onDelete: { checkpoint in
                            pendingCheckpointDeletion = checkpoint
                        }
                    )
                }
                .padding(.top, 12)
                .transition(.opacity.combined(with: .move(edge: .top)))
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding(18)
        .meterCard(tint: .orange, fillOpacity: 0.18, strokeOpacity: 0.24)
    }

    private func batteryPreviewBar(
        startPercent: Double,
        endPercent: Double,
        checkpoints: [ChargeCheckpointSummary],
        isEstimatedEnd: Bool
    ) -> some View {
        let startFrac = CGFloat(max(0, min(startPercent, 100)) / 100)
        let endFrac   = CGFloat(max(0, min(endPercent,   100)) / 100)
        let color = batteryColor(for: endPercent)

        return HStack(spacing: 6) {
            Text("\(Int(startPercent.rounded()))%")
                .font(.caption2.weight(.semibold))
                .foregroundColor(.secondary)
                .monospacedDigit()
                .frame(minWidth: 26, alignment: .trailing)

            GeometryReader { geo in
                let w = geo.size.width
                ZStack(alignment: .leading) {
                    Capsule()
                        .fill(Color.primary.opacity(0.10))

                    Rectangle()
                        .fill(color.opacity(isEstimatedEnd ? 0.45 : 0.72))
                        .frame(width: max(w * (endFrac - startFrac), 3))
                        .offset(x: w * startFrac)

                    ForEach(checkpoints, id: \.id) { cp in
                        let xFrac = CGFloat(max(0, min(cp.batteryPercent, 100)) / 100)
                        let isFinal = cp.flag == .final
                        Rectangle()
                            .fill(Color.white.opacity(isFinal ? 0.95 : 0.70))
                            .frame(width: isFinal ? 2.0 : 1.5, height: 10)
                            .offset(x: w * xFrac - (isFinal ? 1.0 : 0.75))
                    }
                }
                .clipShape(Capsule())
            }
            .frame(height: 8)

            HStack(spacing: 1) {
                if isEstimatedEnd {
                    Text("~")
                        .font(.caption2)
                        .foregroundColor(.secondary)
                }
                Text("\(Int(endPercent.rounded()))%")
                    .font(.caption2.weight(.semibold))
                    .foregroundColor(isEstimatedEnd ? .secondary : color)
                    .monospacedDigit()
            }
            .frame(minWidth: 32, alignment: .leading)
        }
    }

    private func batteryGaugeSection(
        prediction: BatteryLevelPrediction,
        session: ChargeSessionSummary,
        displayedEnergyWh: Double
    ) -> some View {
        let percent = prediction.predictedPercent
        let color = batteryColor(for: percent)
        let duration = displayedSessionDuration(for: session)
        let rateWhPerSec: Double? = duration > 300 && displayedEnergyWh > 0.01
            ? displayedEnergyWh / duration
            : nil
        let etaToFull = etaText(
            rateWhPerSec: rateWhPerSec,
            remainingWh: max((prediction.energyWh(forPercent: 100) ?? displayedEnergyWh) - displayedEnergyWh, 0),
            isRelevant: percent < 98 && prediction.estimatedCapacityWh != nil
        )
        let etaToTarget = etaToTargetText(
            session: session,
            prediction: prediction,
            displayedEnergyWh: displayedEnergyWh,
            rateWhPerSec: rateWhPerSec
        )

        return VStack(spacing: 10) {
            HStack(alignment: .lastTextBaseline, spacing: 8) {
                HStack(alignment: .lastTextBaseline, spacing: 3) {
                    Text("\(Int(percent.rounded()))")
                        .font(.system(size: 52, weight: .bold, design: .rounded))
                        .foregroundColor(color)
                        .monospacedDigit()
                    Text("%")
                        .font(.title2.weight(.semibold))
                        .foregroundColor(color.opacity(0.8))
                }

                Spacer()

                if let estimatedCapacityWh = prediction.estimatedCapacityWh {
                    VStack(alignment: .trailing, spacing: 2) {
                        Text("\(estimatedCapacityWh.format(decimalDigits: 2)) Wh")
                            .font(.callout.weight(.bold))
                            .foregroundColor(.orange)
                            .monospacedDigit()
                        Text(prediction.basis.metricLabel)
                            .font(.caption2)
                            .foregroundColor(.secondary)
                    }
                }
            }

            batteryProgressBar(
                percent: percent,
                startPercent: session.startBatteryPercent,
                targetPercent: session.targetBatteryPercent
            )

            HStack(spacing: 14) {
                if let etaToFull {
                    etaPill(icon: "clock.fill", tint: .green, value: etaToFull, label: "to full")
                }

                if let etaToTarget, let target = session.targetBatteryPercent {
                    etaPill(
                        icon: "bell.badge.fill",
                        tint: .indigo,
                        value: etaToTarget,
                        label: "to \(Int(target.rounded()))%"
                    )
                }

                Spacer()

                Text("anchored to \(prediction.anchorDescription) at \(prediction.anchorPercent.format(decimalDigits: 0))%")
                    .font(.caption2)
                    .foregroundColor(.secondary)
                    .multilineTextAlignment(.trailing)
            }
        }
        .padding(14)
        .meterCard(tint: color, fillOpacity: 0.10, strokeOpacity: 0.16, cornerRadius: 16)
    }

    private func etaPill(icon: String, tint: Color, value: String, label: String) -> some View {
        VStack(alignment: .leading, spacing: 1) {
            HStack(spacing: 4) {
                Image(systemName: icon)
                    .font(.caption)
                    .foregroundColor(tint)
                Text(value)
                    .font(.caption.weight(.bold))
            }
            Text(label)
                .font(.caption2)
                .foregroundColor(.secondary)
        }
    }

    private func batteryProgressBar(
        percent: Double,
        startPercent: Double?,
        targetPercent: Double?
    ) -> some View {
        let color = batteryColor(for: percent)
        return GeometryReader { geo in
            let width = geo.size.width
            ZStack(alignment: .leading) {
                Capsule()
                    .fill(Color.primary.opacity(0.10))
                Rectangle()
                    .fill(
                        LinearGradient(
                            colors: [color.opacity(0.6), color],
                            startPoint: .leading,
                            endPoint: .trailing
                        )
                    )
                    .frame(width: max(width * CGFloat(percent / 100), 4))
                    .animation(.easeInOut(duration: 0.4), value: percent)
                if let start = startPercent, start > 2, start < 98 {
                    Rectangle()
                        .fill(Color.white.opacity(0.55))
                        .frame(width: 2, height: 20)
                        .offset(x: width * CGFloat(start / 100) - 1)
                }
                if let target = targetPercent {
                    Rectangle()
                        .fill(Color.indigo.opacity(0.9))
                        .frame(width: 2.5, height: 20)
                        .offset(x: width * CGFloat(target / 100) - 1.25)
                }
            }
            .clipShape(Capsule())
        }
        .frame(height: 20)
    }

    private func sessionMetricsGrid(
        session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary,
        displayedEnergyWh: Double,
        hasPrediction: Bool
    ) -> some View {
        let capacityFallback: Double? = hasPrediction ? nil : (
            session.capacityEstimateWh
                ?? chargedDevice.estimatedBatteryCapacityWh(for: session.chargingTransportMode)
                ?? chargedDevice.estimatedBatteryCapacityWh
        )
        let columns = [GridItem(.flexible()), GridItem(.flexible())]

        return LazyVGrid(columns: columns, spacing: 8) {
            metricCell(label: "Energy", value: "\(displayedEnergyWh.format(decimalDigits: 3)) Wh", tint: .blue)
            metricCell(label: "Duration", value: formatDuration(displayedSessionDuration(for: session)), tint: .teal)

            if shouldShowChargingTransport(for: session, chargedDevice: chargedDevice) {
                metricCell(label: "Type", value: session.chargingTransportMode.title, tint: .orange)
            }
            if shouldShowChargingState(for: session, chargedDevice: chargedDevice) {
                metricCell(label: "Mode", value: session.chargingStateMode.title, tint: .purple)
            }

            metricCell(label: "Auto Stop", value: autoStopLabel(for: session), tint: .secondary)

            if let capacityFallback {
                metricCell(label: "Est. Capacity", value: "\(capacityFallback.format(decimalDigits: 2)) Wh", tint: .orange)
            }
        }
    }

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

    private func completionConfirmationCard(_ session: ChargeSessionSummary) -> some View {
        VStack(alignment: .leading, spacing: 10) {
            Label("Charging may have stopped", systemImage: "questionmark.circle.fill")
                .font(.subheadline.weight(.semibold))

            if let contradictionPercent = session.completionContradictionPercent {
                Text("Current dropped but estimated level is only \(contradictionPercent.format(decimalDigits: 0))%.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            } else {
                Text("Current dropped to the stop threshold but the prediction doesn't confirm a full charge yet.")
                    .font(.caption)
                    .foregroundColor(.secondary)
            }

            HStack(spacing: 10) {
                Button("Finish") {
                    beginStopConfirmation(for: session)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 9)
                .meterCard(tint: .orange, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
                .buttonStyle(.plain)

                Button("Keep Monitoring") {
                    _ = appData.continueChargeSessionMonitoring(sessionID: session.id)
                }
                .frame(maxWidth: .infinity)
                .padding(.vertical, 9)
                .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 targetSectionView(session: ChargeSessionSummary, predictedPercent: Double?) -> some View {
        let draftBelowPrediction: Bool = {
            guard let draft = parsedDraftTarget, let predictedPercent else { return false }
            return draft <= predictedPercent
        }()
        let savedBelowPrediction: Bool = {
            guard let saved = session.targetBatteryPercent, let predictedPercent else { return false }
            return saved <= predictedPercent
        }()

        return HStack(alignment: .center, spacing: 8) {
            Image(systemName: "bell.badge")
                .foregroundColor(.indigo)
                .font(.subheadline)

            Text("Notify at")
                .font(.subheadline.weight(.semibold))

            Spacer(minLength: 8)

            if showingInlineTargetEditor {
                targetEditorControls(
                    session: session,
                    draftBelowPrediction: draftBelowPrediction,
                    predictedPercent: predictedPercent
                )
            } else {
                savedTargetControls(
                    session: session,
                    savedBelowPrediction: savedBelowPrediction,
                    predictedPercent: predictedPercent
                )
            }
        }
    }

    private func targetEditorControls(
        session: ChargeSessionSummary,
        draftBelowPrediction: Bool,
        predictedPercent: Double?
    ) -> some View {
        Group {
            Button {
                let current = parsedDraftTarget ?? session.targetBatteryPercent ?? 80
                draftTargetText = max(current - 1, 1).format(decimalDigits: 0)
            } label: {
                Image(systemName: "minus.circle")
                    .font(.title3)
            }
            .buttonStyle(.plain)

            TextField("-", text: $draftTargetText)
                .keyboardType(.decimalPad)
                .textFieldStyle(.roundedBorder)
                .frame(width: 48)
                .multilineTextAlignment(.center)
                .foregroundColor(draftBelowPrediction ? .orange : .primary)

            Text("%")
                .font(.subheadline)
                .foregroundColor(.secondary)

            if draftBelowPrediction, let predictedPercent {
                predictionWarningButton(predictedPercent: predictedPercent)
            }

            Button {
                let current = parsedDraftTarget ?? session.targetBatteryPercent ?? 80
                draftTargetText = min(current + 1, 100).format(decimalDigits: 0)
            } label: {
                Image(systemName: "plus.circle")
                    .font(.title3)
            }
            .buttonStyle(.plain)

            Button {
                if let value = parsedDraftTarget {
                    _ = appData.setTargetBatteryPercent(value, for: session.id)
                }
                showingInlineTargetEditor = false
            } label: {
                Image(systemName: "checkmark.circle.fill")
                    .foregroundColor(parsedDraftTarget != nil ? .indigo : .secondary)
                    .font(.title3)
            }
            .buttonStyle(.plain)
            .disabled(parsedDraftTarget == nil)

            Button {
                showingInlineTargetEditor = false
                draftTargetText = ""
            } label: {
                Image(systemName: "xmark.circle")
                    .foregroundColor(.secondary)
                    .font(.title3)
            }
            .buttonStyle(.plain)
        }
    }

    private func savedTargetControls(
        session: ChargeSessionSummary,
        savedBelowPrediction: Bool,
        predictedPercent: Double?
    ) -> some View {
        Group {
            if let targetPercent = session.targetBatteryPercent {
                Text("\(targetPercent.format(decimalDigits: 0))%")
                    .font(.subheadline.weight(.semibold))
                    .foregroundColor(savedBelowPrediction ? .orange : .indigo)

                if savedBelowPrediction, let predictedPercent {
                    predictionWarningButton(predictedPercent: predictedPercent)
                }

                Button {
                    _ = appData.setTargetBatteryPercent(nil, for: session.id)
                } label: {
                    Image(systemName: "xmark.circle.fill")
                        .foregroundColor(.secondary)
                        .font(.callout)
                }
                .buttonStyle(.plain)
                .help("Remove alert")
            }

            Button {
                draftTargetText = session.targetBatteryPercent.map {
                    $0.format(decimalDigits: 0)
                } ?? "80"
                showingInlineTargetEditor = true
            } label: {
                Image(systemName: session.targetBatteryPercent == nil ? "plus" : "pencil")
                    .font(.caption.weight(.semibold))
                    .frame(width: 30, height: 30)
                    .contentShape(Rectangle())
            }
            .meterCard(tint: .indigo, fillOpacity: 0.12, strokeOpacity: 0.18, cornerRadius: 10)
            .buttonStyle(.plain)
            .help(session.targetBatteryPercent == nil ? "Set battery alert" : "Edit battery alert")
        }
    }

    private func predictionWarningButton(predictedPercent: Double) -> some View {
        Button {} label: {
            Image(systemName: "exclamationmark.triangle.fill")
                .font(.callout.weight(.semibold))
                .foregroundColor(.orange)
        }
        .buttonStyle(.plain)
        .help("Battery is already predicted at \(predictedPercent.format(decimalDigits: 0))% - this alert won't fire. Raise the value or add a checkpoint to correct the prediction.")
    }

    private func monitoringActionRow(_ session: ChargeSessionSummary) -> some View {
        HStack(spacing: 10) {
            if session.status == .active {
                Button("Pause") {
                    _ = appData.pauseChargeSession(sessionID: session.id, from: liveMonitoringMeter)
                }
                .monitoringActionStyle(tint: .orange)
            } else if session.status == .paused {
                Button("Resume") {
                    _ = appData.resumeChargeSession(sessionID: session.id, from: liveMonitoringMeter)
                }
                .monitoringActionStyle(tint: .blue)
            }

            Button("Terminate Session") {
                beginStopConfirmation(for: session)
            }
            .monitoringActionStyle(tint: .red)
        }
    }

    private func stopConfirmPanel(
        session: ChargeSessionSummary,
        displayedEnergyWh: Double
    ) -> some View {
        let canSave = hasSavableChargeData(
            session: session,
            displayedEnergyWh: displayedEnergyWh
        )
        let saveDisabledReason = saveDisabledReason(
            session: session,
            displayedEnergyWh: displayedEnergyWh
        )
        let isSaveEnabled = saveDisabledReason == nil

        return VStack(alignment: .leading, spacing: 12) {
            HStack {
                Text("Final Checkpoint")
                    .font(.subheadline.weight(.semibold))
                Text("optional")
                    .font(.caption2.weight(.semibold))
                    .foregroundColor(.secondary)
            }

            finalCheckpointPicker(session)

            if finalCheckpointMode == .custom {
                customFinalCheckpointRow
            }

            if let saveDisabledReason {
                Label(saveDisabledReason, systemImage: "exclamationmark.triangle.fill")
                    .font(.caption)
                    .foregroundColor(.red)
                    .fixedSize(horizontal: false, vertical: true)
            } else if let stopFailureMessage {
                Label(stopFailureMessage, systemImage: "exclamationmark.triangle.fill")
                    .font(.caption)
                    .foregroundColor(.red)
                    .fixedSize(horizontal: false, vertical: true)
            } else if finalCheckpointMode == .custom, let parsedFinalCheckpoint {
                Label("Final checkpoint will be saved at \(parsedFinalCheckpoint.format(decimalDigits: 0))%.", systemImage: "checkmark.circle.fill")
                    .font(.caption)
                    .foregroundColor(.green)
                    .fixedSize(horizontal: false, vertical: true)
            }

            HStack(spacing: 8) {
                Button("Discard") {
                    discardSession(session)
                }
                .monitoringPanelActionStyle(tint: .secondary)

                Button {
                    stopSession(
                        session,
                        displayedEnergyWh: displayedEnergyWh
                    )
                } label: {
                    Label("Save Session", systemImage: "checkmark.circle.fill")
                        .frame(maxWidth: .infinity)
                }
                .monitoringPanelActionStyle(tint: isSaveEnabled ? .green : .secondary, isProminent: isSaveEnabled)
                .disabled(!isSaveEnabled)
                .help(saveDisabledReason ?? "Close and save this session")

                Button("Cancel") {
                    resetStopConfirmation()
                }
                .monitoringPanelActionStyle(tint: .secondary)
            }
        }
        .padding(14)
        .meterCard(tint: canSave ? .green : .red, fillOpacity: 0.06, strokeOpacity: 0.14, cornerRadius: 16)
    }

    private func finalCheckpointPicker(_ session: ChargeSessionSummary) -> some View {
        return HStack(spacing: 8) {
            ForEach([FinalCheckpoint.full, .skip, .custom], id: \.self) { mode in
                Button {
                    finalCheckpointMode = mode
                    if mode == .custom {
                        prefillFinalCheckpointIfNeeded(for: session)
                    } else {
                        finalCheckpointText = ""
                    }
                } label: {
                    VStack(spacing: 5) {
                        Image(systemName: mode.icon)
                            .font(.title3)
                            .foregroundColor(finalCheckpointMode == mode ? .primary : .secondary)
                        Text(mode.label)
                            .font(.caption.weight(.semibold))
                            .foregroundColor(finalCheckpointMode == mode ? .primary : .secondary)
                    }
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 10)
                    .background(finalCheckpointMode == mode ? Color.primary.opacity(0.10) : Color.clear)
                    .meterCard(
                        tint: finalCheckpointMode == mode ? .primary : .secondary,
                        fillOpacity: finalCheckpointMode == mode ? 0.08 : 0.04,
                        strokeOpacity: finalCheckpointMode == mode ? 0.20 : 0.10,
                        cornerRadius: 12
                    )
                }
                .buttonStyle(.plain)
            }
        }
    }

    private var customFinalCheckpointRow: some View {
        let isInvalid = finalCheckpointText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
            || parsedFinalCheckpoint == nil

        return HStack(spacing: 8) {
            Button {
                adjustFinalCheckpoint(by: -1)
            } label: {
                Image(systemName: "minus.circle").font(.title3)
            }
            .buttonStyle(.plain)

            TextField("-", text: $finalCheckpointText)
                .keyboardType(.decimalPad)
                .textFieldStyle(.roundedBorder)
                .frame(width: 56)
                .multilineTextAlignment(.center)
                .overlay(
                    RoundedRectangle(cornerRadius: 6)
                        .stroke(isInvalid ? Color.red.opacity(0.75) : Color.clear, lineWidth: 1)
                )

            Text("%").foregroundColor(.secondary)

            Text("required")
                .font(.caption2.weight(.semibold))
                .foregroundColor(isInvalid ? .red : .secondary)

            Button {
                adjustFinalCheckpoint(by: 1)
            } label: {
                Image(systemName: "plus.circle").font(.title3)
            }
            .buttonStyle(.plain)

            Spacer()
        }
    }

    private func followerNoticeCard(_ session: ChargeSessionSummary) -> some View {
        MeterInfoCardView(title: "Monitoring Device", tint: .secondary) {
            if let meterName = session.meterName {
                MeterInfoRowView(label: "Controlled On", value: meterName)
            }
            Text("Pause, stop, checkpoint, and live trim controls are available on the device connected to the monitoring meter.")
                .font(.caption2)
                .foregroundColor(.secondary)
        }
    }

    private func managementCard(_ session: ChargeSessionSummary) -> some View {
        MeterInfoCardView(title: "Administration", tint: .red) {
            Button(role: .destructive) {
                pendingSessionDeletion = session
            } label: {
                Label("Delete Session", systemImage: "trash")
                    .font(.subheadline.weight(.semibold))
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 10)
                    .meterCard(tint: .red, fillOpacity: 0.12, strokeOpacity: 0.20, cornerRadius: 14)
            }
            .buttonStyle(.plain)
        }
    }

    @ViewBuilder
    private func trimDetectionBanner(_ session: ChargeSessionSummary) -> some View {
        if let window = detectedTrimWindow {
            HStack(spacing: 12) {
                Image(systemName: "scissors.circle.fill")
                    .font(.title3)
                    .foregroundColor(.blue)

                VStack(alignment: .leading, spacing: 2) {
                    Text("Charging ended early")
                        .font(.subheadline.weight(.semibold))
                    Text("Active charging was detected between \(window.start.format(as: "HH:mm")) and \(window.end.format(as: "HH:mm")).")
                        .font(.caption)
                        .foregroundColor(.secondary)
                        .fixedSize(horizontal: false, vertical: true)
                }

                Spacer(minLength: 0)

                VStack(spacing: 6) {
                    Button("Trim Start") {
                        setSessionTrim(sessionID: session.id, start: window.start, end: session.trimEnd)
                        trimBannerDismissedForSessionID = session.id
                    }
                    .font(.caption.weight(.semibold))
                    .buttonStyle(.borderedProminent)
                    .controlSize(.small)
                    .tint(.blue)

                    Button("End & Finish") {
                        requestStop(
                            session,
                            applyingTrimStart: session.trimStart ?? window.start,
                            trimEnd: window.end,
                            title: "Trim End & Finish",
                            confirmTitle: "Finish",
                            explanation: "The detected charging window will be saved before the session is closed."
                        )
                        trimBannerDismissedForSessionID = session.id
                    }
                    .font(.caption.weight(.semibold))
                    .buttonStyle(.bordered)
                    .controlSize(.small)
                    .tint(.red)
                }
            }
            .padding(14)
            .background(
                RoundedRectangle(cornerRadius: 14)
                    .fill(Color.blue.opacity(0.10))
                    .overlay(RoundedRectangle(cornerRadius: 14).stroke(Color.blue.opacity(0.22), lineWidth: 1))
            )
            .transition(.opacity.combined(with: .move(edge: .top)))
        }
    }

    private func shouldShowSessionChart(_ session: ChargeSessionSummary) -> Bool {
        !session.aggregatedSamples.isEmpty || liveMonitoringMeter != nil
    }

    private func chartCard(
        _ session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> some View {
        ChargeSessionChartCardView(
            session: session,
            monitoringMeter: liveMonitoringMeter,
            batteryPercentPoints: batteryPercentChartPoints(
                for: session,
                chargedDevice: chargedDevice
            ),
            controlMode: chartControlMode(for: session),
            onSetTrim: { start, end in
                setSessionTrim(sessionID: session.id, start: start, end: end)
            },
            onStopWithTrim: { start, end in
                requestStop(
                    session,
                    applyingTrimStart: start,
                    trimEnd: end,
                    title: "Trim End & Finish",
                    confirmTitle: "Finish",
                    explanation: "The selected chart window will be saved as this session's active charging window before the session is closed."
                )
            },
            onCommitTrim: (session.status.isOpen == false && session.isTrimmed)
                ? {
                    pendingTrimCommitSession = session
                }
                : nil
        )
    }

    private func chartControlMode(for session: ChargeSessionSummary) -> ChargeSessionChartControlMode {
        if hasMonitoringControls {
            return .activeMonitoring
        }

        if session.status.isOpen == false {
            return .closed
        }

        return .none
    }

    private func setSessionTrim(sessionID: UUID, start: Date?, end: Date?) {
        _ = appData.setSessionTrim(sessionID: sessionID, start: start, end: end)
        trimBannerDismissedForSessionID = sessionID
    }

    private func batteryPercentChartPoints(
        for session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> [Measurements.Measurement.Point] {
        var candidates: [BatteryPercentCandidate] = []

        for sample in session.displayedAggregatedSamples {
            let percent = chargedDevice.batteryLevelPrediction(
                    for: session,
                    effectiveEnergyWhOverride: effectiveBatteryEnergyWh(
                        rawMeasuredEnergyWh: sample.measuredEnergyWh,
                        for: session
                    ),
                    referenceTimestamp: sample.timestamp
                )?.predictedPercent
                ?? sample.estimatedBatteryPercent

            if let percent, percent.isFinite {
                candidates.append(
                    BatteryPercentCandidate(
                        timestamp: sample.timestamp,
                        percent: percent,
                        isCheckpoint: false
                    )
                )
            }
        }

        for checkpoint in session.checkpoints where session.effectiveTimeRange.contains(checkpoint.timestamp) {
            guard checkpoint.batteryPercent.isFinite,
                  checkpoint.batteryPercent >= 0,
                  checkpoint.batteryPercent <= 100 else {
                continue
            }
            candidates.append(
                BatteryPercentCandidate(
                    timestamp: checkpoint.timestamp,
                    percent: checkpoint.batteryPercent,
                    isCheckpoint: true
                )
            )
        }

        if hasMonitoringControls,
           let prediction = chargedDevice.batteryLevelPrediction(
            for: session,
            effectiveEnergyWhOverride: displayedSessionEnergyWh(for: session)
           ) {
            candidates.append(
                BatteryPercentCandidate(
                    timestamp: max(session.lastObservedAt, Date()),
                    percent: prediction.predictedPercent,
                    isCheckpoint: false
                )
            )
        }

        let sortedCandidates = coalescedBatteryPercentCandidates(candidates).sorted { lhs, rhs in
            if lhs.timestamp != rhs.timestamp {
                return lhs.timestamp < rhs.timestamp
            }
            return lhs.isCheckpoint && !rhs.isCheckpoint
        }

        var points: [Measurements.Measurement.Point] = []
        var previousCandidate: BatteryPercentCandidate?

        for candidate in sortedCandidates {
            if let previousCandidate,
               candidate.timestamp.timeIntervalSince(previousCandidate.timestamp) > 90 {
                points.append(
                    Measurements.Measurement.Point(
                        id: points.count,
                        timestamp: candidate.timestamp,
                        value: points.last?.value ?? candidate.percent,
                        kind: .discontinuity
                    )
                )
            }

            points.append(
                Measurements.Measurement.Point(
                    id: points.count,
                    timestamp: candidate.timestamp,
                    value: min(max(candidate.percent, 0), 100)
                )
            )
            previousCandidate = candidate
        }

        return points
    }

    private func coalescedBatteryPercentCandidates(
        _ candidates: [BatteryPercentCandidate]
    ) -> [BatteryPercentCandidate] {
        let sortedCandidates = candidates.sorted { lhs, rhs in
            if lhs.timestamp != rhs.timestamp {
                return lhs.timestamp < rhs.timestamp
            }
            return lhs.isCheckpoint && !rhs.isCheckpoint
        }

        var coalesced: [BatteryPercentCandidate] = []

        for candidate in sortedCandidates {
            if let last = coalesced.last,
               abs(candidate.timestamp.timeIntervalSince(last.timestamp)) <= 1 {
                if candidate.isCheckpoint || !last.isCheckpoint {
                    coalesced[coalesced.count - 1] = candidate
                }
            } else {
                coalesced.append(candidate)
            }
        }

        return coalesced
    }

    private func effectiveBatteryEnergyWh(
        rawMeasuredEnergyWh: Double,
        for session: ChargeSessionSummary
    ) -> Double {
        switch session.chargingTransportMode {
        case .wired:
            return rawMeasuredEnergyWh
        case .wireless:
            if let factor = session.wirelessEfficiencyFactor, factor > 0 {
                return rawMeasuredEnergyWh * factor
            }
            if let effectiveEnergyWh = session.effectiveBatteryEnergyWh,
               session.measuredEnergyWh > 0 {
                return rawMeasuredEnergyWh * (effectiveEnergyWh / session.measuredEnergyWh)
            }
            return rawMeasuredEnergyWh
        }
    }

    private func requestStop(
        _ session: ChargeSessionSummary,
        applyingTrimStart trimStart: Date?,
        trimEnd: Date?,
        title: String,
        confirmTitle: String,
        explanation: String
    ) {
        pendingSessionStopRequest = ChargeSessionStopRequest(
            sessionID: session.id,
            title: title,
            confirmTitle: confirmTitle,
            explanation: explanation,
            appliesTrim: trimStart != nil || trimEnd != nil,
            trimStart: trimStart,
            trimEnd: trimEnd
        )
    }

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

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

    private var resolvedFinalCheckpoint: Double? {
        switch finalCheckpointMode {
        case .full:   return suggestedFinalCheckpointPercent(for: session)
        case .skip:   return nil
        case .custom: return parsedFinalCheckpoint
        }
    }

    private func adjustFinalCheckpoint(by delta: Double) {
        let current = parsedFinalCheckpoint ?? suggestedFinalCheckpointPercent(for: session) ?? 0
        let next = min(max(current + delta, 0), 100)
        finalCheckpointText = next.format(decimalDigits: 0)
    }

    private func suggestedFinalCheckpointPercent(for session: ChargeSessionSummary?) -> Double? {
        guard let session else { return nil }
        if let endBatteryPercent = session.endBatteryPercent {
            return endBatteryPercent
        }
        if let latestCheckpoint = session.checkpoints.max(by: { $0.timestamp < $1.timestamp }) {
            return latestCheckpoint.batteryPercent
        }
        return session.targetBatteryPercent ?? session.completionContradictionPercent
    }

    private func prefillFinalCheckpointIfNeeded(for session: ChargeSessionSummary) {
        guard finalCheckpointText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
              let suggestedPercent = suggestedFinalCheckpointPercent(for: session) else {
            return
        }
        finalCheckpointText = suggestedPercent.format(decimalDigits: 0)
    }

    private func hasSavableChargeData(
        session: ChargeSessionSummary,
        displayedEnergyWh: Double
    ) -> Bool {
        session.hasSavableChargeData
            || displayedEnergyWh > 0
    }

    private func saveDisabledReason(
        session: ChargeSessionSummary,
        displayedEnergyWh: Double
    ) -> String? {
        if finalCheckpointMode == .custom {
            let trimmed = finalCheckpointText.trimmingCharacters(in: .whitespacesAndNewlines)
            if trimmed.isEmpty {
                return "Enter the final battery percentage or choose Skip."
            }
            if parsedFinalCheckpoint == nil {
                return "Final battery percentage must be between 0 and 100."
            }
        }
        if finalCheckpointMode == .full && resolvedFinalCheckpoint == nil {
            return "Full can be saved only when there is a current battery estimate. Choose Skip or enter the displayed percentage."
        }

        guard hasSavableChargeData(
            session: session,
            displayedEnergyWh: displayedEnergyWh
        ) else {
            return "This session has no charging data to save. Discard it instead."
        }

        return nil
    }

    private func stopSession(
        _ session: ChargeSessionSummary,
        displayedEnergyWh: Double
    ) {
        stopFailureMessage = nil

        if let saveDisabledReason = saveDisabledReason(
            session: session,
            displayedEnergyWh: displayedEnergyWh
        ) {
            stopFailureMessage = saveDisabledReason
            return
        }

        let didSave = appData.stopChargeSession(
            sessionID: session.id,
            finalBatteryPercent: resolvedFinalCheckpoint,
            from: liveMonitoringMeter
        )
        if didSave {
            resetStopConfirmation()
        } else {
            stopFailureMessage = "The session could not be closed. Live readings were flushed, but the stored session did not accept the save yet. Try again in a moment."
        }
    }

    private func beginStopConfirmation(for session: ChargeSessionSummary) {
        finalCheckpointMode = .skip
        finalCheckpointText = ""
        stopFailureMessage = nil
        showingStopConfirm = true
    }

    private func discardSession(_ session: ChargeSessionSummary) {
        _ = appData.deleteChargeSession(sessionID: session.id)
        resetStopConfirmation()
    }

    private func resetStopConfirmation() {
        showingStopConfirm = false
        finalCheckpointText = ""
        finalCheckpointMode = .skip
        stopFailureMessage = nil
    }

    private func syncMonitoringRestore() {
        guard let session,
              session.status.isOpen,
              let liveMonitoringMeter,
              session.meterMACAddress == liveMonitoringMeter.btSerial.macAddress.description else {
            return
        }
        liveMonitoringMeter.restoreChargeMonitoringIfNeeded(from: session)
    }

    private func runTrimDetection() {
        guard hasMonitoringControls,
              let session,
              session.isTrimmed == false,
              !session.aggregatedSamples.isEmpty else {
            detectedTrimWindow = nil
            return
        }

        let sessionEnd = session.endedAt ?? session.lastObservedAt
        detectedTrimWindow = ChargingWindowDetector.detect(
            samples: session.aggregatedSamples,
            sessionStart: session.startedAt,
            sessionEnd: sessionEnd
        )
    }

    private func displayedSessionEnergyWh(for session: ChargeSessionSummary) -> Double {
        let storedEnergyWh = session.effectiveOrMeasuredEnergyWh
        guard session.isTrimmed == false else { return storedEnergyWh }
        guard session.status.isOpen else { return storedEnergyWh }
        guard let liveMonitoringMeter else { return storedEnergyWh }
        guard session.meterMACAddress == liveMonitoringMeter.btSerial.macAddress.description else { return storedEnergyWh }
        if let baselineEnergyWh = session.meterEnergyBaselineWh {
            return max(storedEnergyWh, max(liveMonitoringMeter.recordedWH - baselineEnergyWh, 0))
        }
        return storedEnergyWh
    }

    private func displayedSessionDuration(for session: ChargeSessionSummary) -> TimeInterval {
        let storedDuration = max(session.effectiveDuration, 0)
        guard session.isTrimmed == false else { return storedDuration }
        guard session.status.isOpen else { return storedDuration }
        guard let liveMonitoringMeter else { return storedDuration }
        guard session.meterMACAddress == liveMonitoringMeter.btSerial.macAddress.description else { return storedDuration }
        return max(storedDuration, max(liveMonitoringMeter.chargeRecordDuration, 0))
    }

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

    private func formatDuration(_ duration: TimeInterval) -> String {
        let totalSeconds = Int(duration.rounded(.down))
        let hours = totalSeconds / 3600
        let minutes = (totalSeconds % 3600) / 60
        let seconds = totalSeconds % 60
        if hours > 0 {
            return String(format: "%d:%02d:%02d", hours, minutes, seconds)
        }
        return String(format: "%02d:%02d", minutes, seconds)
    }

    private func autoStopDescription(for session: ChargeSessionSummary) -> String {
        if session.autoStopEnabled == false {
            return "Manual"
        }

        if let sessionWarning = sessionWarning(for: session),
           sessionWarning.contains("idle-current") {
            return "Blocked by charger setup"
        }

        if session.stopThresholdAmps > 0 {
            return "\(session.stopThresholdAmps.format(decimalDigits: 2)) A"
        }

        return "Learning"
    }

    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 shouldShowChargingTransport(
        for session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> Bool {
        chargedDevice.supportedChargingModes.count > 1
            || chargedDevice.supportedChargingModes.contains(session.chargingTransportMode) == false
    }

    private func shouldShowChargingState(
        for session: ChargeSessionSummary,
        chargedDevice: ChargedDeviceSummary
    ) -> Bool {
        chargedDevice.supportedChargingStateModes.count > 1
            || chargedDevice.supportedChargingStateModes.contains(session.chargingStateMode) == false
    }

    private func batteryColor(for percent: Double) -> Color {
        if percent >= 75 { return .green }
        if percent >= 35 { return .orange }
        return .red
    }

    private func etaText(
        rateWhPerSec: Double?,
        remainingWh: Double,
        isRelevant: Bool
    ) -> String? {
        guard isRelevant, let rateWhPerSec, rateWhPerSec > 0.0001 else { return nil }
        let seconds = remainingWh / rateWhPerSec
        return seconds > 120 ? formatETA(seconds) : nil
    }

    private func etaToTargetText(
        session: ChargeSessionSummary,
        prediction: BatteryLevelPrediction,
        displayedEnergyWh: Double,
        rateWhPerSec: Double?
    ) -> String? {
        guard let target = session.targetBatteryPercent, target > prediction.predictedPercent + 1 else {
            return nil
        }
        guard let targetEnergyWh = prediction.energyWh(forPercent: target) else {
            return nil
        }
        return etaText(
            rateWhPerSec: rateWhPerSec,
            remainingWh: max(targetEnergyWh - displayedEnergyWh, 0),
            isRelevant: true
        )
    }

    private func batteryPredictionExplanation(_ prediction: BatteryLevelPrediction) -> String {
        let anchor = "Anchored to \(prediction.anchorDescription) at \(prediction.anchorPercent.format(decimalDigits: 0))%"
        guard let estimatedCapacityWh = prediction.estimatedCapacityWh else {
            return "\(anchor)."
        }
        return "\(anchor) using \(estimatedCapacityWh.format(decimalDigits: 2)) Wh \(prediction.basis.explanatoryLabel)."
    }

    private func formatETA(_ seconds: TimeInterval) -> String {
        let totalMinutes = Int(seconds / 60)
        if totalMinutes < 60 { return "\(totalMinutes)m" }
        let hours = totalMinutes / 60
        let minutes = totalMinutes % 60
        return minutes == 0 ? "\(hours)h" : "\(hours)h \(minutes)m"
    }

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

    private func sessionWarning(for session: ChargeSessionSummary) -> String? {
        nil
    }

    private func wirelessSessionHint(for session: ChargeSessionSummary) -> String? {
        guard session.chargingTransportMode == .wireless else {
            return nil
        }

        var components: [String] = []
        if let wirelessEfficiencyFactor = session.wirelessEfficiencyFactor {
            components.append("Efficiency \(Int((wirelessEfficiencyFactor * 100).rounded()))%")
        }
        if session.usesEstimatedWirelessEfficiency {
            components.append("Estimated from wired baseline and checkpoints")
        }
        if session.shouldWarnAboutLowWirelessEfficiency {
            components.append("Low wireless efficiency, so capacity confidence is reduced")
        }

        return components.isEmpty ? nil : components.joined(separator: " - ")
    }

    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
        }
    }
}

enum ChargeSessionChartControlMode {
    case none
    case activeMonitoring
    case closed
}

struct ChargeSessionChartCardView: View {
    let session: ChargeSessionSummary
    let monitoringMeter: Meter?
    let batteryPercentPoints: [Measurements.Measurement.Point]
    let controlMode: ChargeSessionChartControlMode
    let onSetTrim: (Date?, Date?) -> Void
    let onStopWithTrim: (Date?, Date?) -> Void
    let onCommitTrim: (() -> Void)?

    @StateObject private var storedMeasurements = Measurements()

    private var chartMeasurements: Measurements {
        if let monitoringMeter,
           session.status.isOpen,
           session.meterMACAddress == monitoringMeter.btSerial.macAddress.description {
            return monitoringMeter.chargeRecordMeasurements
        }
        return storedMeasurements
    }

    private var fullTimeRange: ClosedRange<Date> {
        let start = session.startedAt
        let end = max(session.endedAt ?? session.lastObservedAt, start)
        return start...end
    }

    private var fixedTimeRange: ClosedRange<Date>? {
        if monitoringMeter != nil && session.status.isOpen {
            return nil
        }
        return session.effectiveTimeRange
    }

    private var liveTrimBounds: (lower: Date?, upper: Date?) {
        guard monitoringMeter != nil && session.status.isOpen else {
            return (nil, nil)
        }
        return (session.trimStart, session.trimEnd)
    }

    private var showsRangeSelector: Bool {
        controlMode != .none && !session.aggregatedSamples.isEmpty
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack(spacing: 8) {
                Image(systemName: "chart.xyaxis.line")
                    .foregroundColor(.blue)
                Text("Session Chart")
                    .font(.headline)
                ContextInfoButton(
                    title: "Session Chart",
                    message: chartInfoMessage
                )
                Spacer(minLength: 0)
            }

            MeasurementChartView(
                timeRange: fixedTimeRange,
                timeRangeLowerBound: liveTrimBounds.lower,
                timeRangeUpperBound: liveTrimBounds.upper,
                showsRangeSelector: showsRangeSelector,
                rebasesEnergyToVisibleRangeStart: true,
                extendsTimelineToPresent: false,
                showsTemperatureSeries: false,
                showsBatteryPercentSeries: shouldShowBatteryPercentSeries,
                batteryCheckpoints: session.checkpoints,
                batteryPercentPoints: batteryPercentPoints,
                rangeSelectorConfiguration: rangeSelectorConfiguration
            )
            .environmentObject(chartMeasurements)
            .frame(maxWidth: .infinity, alignment: .topLeading)

            if let onCommitTrim {
                Divider()

                HStack(alignment: .center, spacing: 10) {
                    Label("Save trim permanently", systemImage: "internaldrive")
                        .font(.caption.weight(.semibold))
                        .foregroundColor(.secondary)

                    Spacer(minLength: 0)

                    Button {
                        onCommitTrim()
                    } label: {
                        Label("Save Trim", systemImage: "checkmark.seal")
                            .font(.caption.weight(.semibold))
                    }
                    .buttonStyle(.borderedProminent)
                    .controlSize(.small)
                    .tint(.red)
                }
            }
        }
        .padding(18)
        .meterCard(tint: .blue, fillOpacity: 0.14, strokeOpacity: 0.20)
        .onAppear(perform: restoreStoredMeasurementsIfNeeded)
        .onChange(of: session.id) { _ in
            restoreStoredMeasurementsIfNeeded()
        }
        .onChange(of: session.aggregatedSamples.count) { _ in
            restoreStoredMeasurementsIfNeeded()
        }
        .onChange(of: session.checkpoints.count) { _ in
            restoreStoredMeasurementsIfNeeded()
        }
    }

    private var chartInfoMessage: String {
        if monitoringMeter != nil && session.status.isOpen {
            return "This chart combines the persisted session curve with current live data from this meter."
        }

        return "This chart is scoped to the saved session window for \(session.sessionKind.shortTitle.lowercased()) charging."
    }

    private var shouldShowBatteryPercentSeries: Bool {
        !batteryPercentPoints.isEmpty
    }

    private var rangeSelectorConfiguration: MeasurementChartRangeSelectorConfiguration? {
        switch controlMode {
        case .none:
            return nil
        case .activeMonitoring:
            return MeasurementChartRangeSelectorConfiguration(
                keepAction: MeasurementChartSelectionAction(
                    title: "Trim Start",
                    shortTitle: "Start",
                    systemName: "arrow.right.to.line",
                    tone: .destructive,
                    handler: applyActiveStartTrim
                ),
                removeAction: MeasurementChartSelectionAction(
                    title: "Trim End & Finish",
                    shortTitle: "End",
                    systemName: "arrow.left.to.line",
                    tone: .destructiveProminent,
                    handler: requestActiveEndTrim
                ),
                resetAction: MeasurementChartResetAction(
                    title: "Reset Trim",
                    shortTitle: "Reset",
                    systemName: "arrow.counterclockwise",
                    tone: .reversible,
                    confirmationTitle: "Reset session trim?",
                    confirmationButtonTitle: "Reset trim",
                    handler: {
                        onSetTrim(nil, nil)
                    }
                ),
                exportAction: sessionCSVExportAction
            )
        case .closed:
            return MeasurementChartRangeSelectorConfiguration(
                keepAction: MeasurementChartSelectionAction(
                    title: "Trim Window",
                    shortTitle: "Trim",
                    systemName: "scissors",
                    tone: .destructive,
                    handler: applyClosedTrim
                ),
                removeAction: nil,
                resetAction: MeasurementChartResetAction(
                    title: "Reset Trim",
                    shortTitle: "Reset",
                    systemName: "arrow.counterclockwise",
                    tone: .reversible,
                    confirmationTitle: "Reset session trim?",
                    confirmationButtonTitle: "Reset trim",
                    handler: {
                        onSetTrim(nil, nil)
                    }
                ),
                exportAction: sessionCSVExportAction
            )
        }
    }

    private var sessionCSVExportAction: MeasurementChartExportAction {
        MeasurementChartExportAction(
            title: "Export CSV",
            shortTitle: "CSV",
            systemName: "square.and.arrow.up",
            tone: .reversible,
            fileName: sessionCSVFileName,
            content: sessionCSVContent
        )
    }

    private func sessionCSVFileName(for range: ClosedRange<Date>) -> String {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = .current
        formatter.dateFormat = "yyyyMMdd-HHmmss"

        return [
            "charge-session",
            formatter.string(from: range.lowerBound),
            formatter.string(from: range.upperBound)
        ].joined(separator: "-")
    }

    private func sessionCSVContent(for range: ClosedRange<Date>) -> String {
        let samples = session.aggregatedSamples
            .filter { range.contains($0.timestamp) }
            .sorted { lhs, rhs in
                if lhs.bucketIndex != rhs.bucketIndex {
                    return lhs.bucketIndex < rhs.bucketIndex
                }
                return lhs.timestamp < rhs.timestamp
            }

        let timestampFormatter = ISO8601DateFormatter()
        timestampFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

        var rows: [[String]] = [
            [
                "Timestamp",
                "Elapsed Seconds",
                "Voltage (V)",
                "Current (A)",
                "Power (W)",
                "Session Energy (Wh)",
                "Interval Energy (Wh)",
                "Battery (%)",
                "Sample Count"
            ]
        ]

        let intervalEnergyBaseline = samples.first?.measuredEnergyWh ?? 0
        for sample in samples {
            rows.append([
                timestampFormatter.string(from: sample.timestamp),
                formattedCSVNumber(sample.timestamp.timeIntervalSince(session.startedAt), fractionDigits: 3),
                formattedCSVNumber(sample.averageVoltageVolts, fractionDigits: 6),
                formattedCSVNumber(sample.averageCurrentAmps, fractionDigits: 6),
                formattedCSVNumber(sample.averagePowerWatts, fractionDigits: 6),
                formattedCSVNumber(sample.measuredEnergyWh, fractionDigits: 6),
                formattedCSVNumber(max(sample.measuredEnergyWh - intervalEnergyBaseline, 0), fractionDigits: 6),
                formattedCSVNumber(sample.estimatedBatteryPercent, fractionDigits: 3),
                "\(sample.sampleCount)"
            ])
        }

        return rows
            .map { row in row.map(escapedCSVField).joined(separator: ",") }
            .joined(separator: "\n")
    }

    private func formattedCSVNumber(_ value: Double?, fractionDigits: Int) -> String {
        guard let value, value.isFinite else { return "" }
        return String(
            format: "%.\(fractionDigits)f",
            locale: Locale(identifier: "en_US_POSIX"),
            value
        )
    }

    private func escapedCSVField(_ field: String) -> String {
        let mustQuote = field.contains(",") || field.contains("\"") || field.contains("\n")
        guard mustQuote else { return field }
        return "\"\(field.replacingOccurrences(of: "\"", with: "\"\""))\""
    }

    private func restoreStoredMeasurementsIfNeeded() {
        guard monitoringMeter == nil || session.status.isOpen == false else {
            return
        }
        storedMeasurements.resetSeries()
        _ = storedMeasurements.restorePersistedChargeSessionSamplesIfNeeded(
            from: session,
            replacingLiveBufferIfNeeded: true
        )
    }

    private func applyActiveStartTrim(_ range: ClosedRange<Date>) {
        onSetTrim(normalizedStart(range.lowerBound), session.trimEnd)
    }

    private func requestActiveEndTrim(_ range: ClosedRange<Date>) {
        let start = session.trimStart ?? normalizedStart(range.lowerBound)
        let end = normalizedEnd(range.upperBound)
        onStopWithTrim(start, end)
    }

    private func applyClosedTrim(_ range: ClosedRange<Date>) {
        onSetTrim(normalizedStart(range.lowerBound), normalizedEnd(range.upperBound))
    }

    private func normalizedStart(_ date: Date) -> Date? {
        date.timeIntervalSince(fullTimeRange.lowerBound) <= 1 ? nil : date
    }

    private func normalizedEnd(_ date: Date) -> Date? {
        fullTimeRange.upperBound.timeIntervalSince(date) <= 1 ? nil : date
    }
}

private struct ChargeSessionStopRequest: Identifiable {
    let sessionID: UUID
    let title: String
    let confirmTitle: String
    let explanation: String
    let appliesTrim: Bool
    let trimStart: Date?
    let trimEnd: Date?

    var id: String {
        [
            sessionID.uuidString,
            title,
            trimStart?.timeIntervalSince1970.description ?? "nil",
            trimEnd?.timeIntervalSince1970.description ?? "nil"
        ].joined(separator: "-")
    }
}

private extension View {
    func monitoringActionStyle(tint: Color) -> some View {
        frame(maxWidth: .infinity)
            .padding(.vertical, 10)
            .meterCard(tint: tint, fillOpacity: 0.16, strokeOpacity: 0.22, cornerRadius: 14)
            .buttonStyle(.plain)
    }

    func monitoringPanelActionStyle(tint: Color, isProminent: Bool = false) -> some View {
        frame(maxWidth: .infinity)
            .padding(.vertical, 9)
            .meterCard(
                tint: tint,
                fillOpacity: isProminent ? 0.22 : 0.10,
                strokeOpacity: isProminent ? 0.32 : 0.14,
                cornerRadius: 14
            )
            .buttonStyle(.plain)
    }
}