contents/ui/main.qml

import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasma5support as Plasma5Support
import org.kde.plasma.plasmoid
import "components" as Components
import "controllers" as Controllers
import "Guards.js" as Guards
import "NotificationMemo.js" as NotificationMemo
import "NotificationPlanner.js" as NotificationPlanner
import "PacePresentation.js" as PacePresentation
import "PanelDisplay.js" as PanelDisplay
import "PanelElements.js" as PanelElements
import "PanelProviders.js" as PanelProviders
import "PanelRules.js" as PanelRules
import "PanelTextFit.js" as PanelTextFit
import "PopupSelection.js" as PopupSelection
import "CommandLedger.js" as CommandLedger
import "AccountRequests.js" as AccountRequests
import "CostRefreshPolicy.js" as CostRefreshPolicy
import "CostPresentation.js" as CostPresentation
import "OverviewProviders.js" as OverviewProviders
import "ProviderFallbackQueue.js" as ProviderFallbackQueue
import "ProviderIdentity.js" as ProviderIdentity
import "PrivacyPresentation.js" as PrivacyPresentation
import "ProviderNormalizer.js" as Normalizer
import "ProviderOrder.js" as ProviderOrder
import "ProviderRosterCache.js" as ProviderRosterCache
import "UsageCache.js" as UsageCache
import "QuotaThresholds.js" as QuotaThresholds
import "SafeText.js" as SafeText
import "SessionRefreshPolicy.js" as SessionRefreshPolicy
import "PopupRefreshPolicy.js" as PopupRefreshPolicy
import "ThemeContrast.js" as ThemeContrast
import "UsageDetails.js" as UsageDetails
import "LegacyUsageDashboard.js" as LegacyUsageDashboard

PlasmoidItem {
    id: root

    Plasmoid.icon: "view-statistics"
    Plasmoid.title: "CodexBar"
    toolTipMainText: Plasmoid.title
    toolTipSubText: panelToolTipText()
    toolTipTextFormat: Text.PlainText
    Plasmoid.contextualActions: [
        PlasmaCore.Action {
            text: i18n("Refresh")
            icon.name: "view-refresh"
            onTriggered: root.refreshNow(true)
        }
    ]

    property string commandPath: (Plasmoid.configuration.commandPath || "codexbar").trim()
    property string provider: (Plasmoid.configuration.provider || "").trim()
    property string source: (Plasmoid.configuration.source || "").trim()
    property int refreshIntervalSec: isFinite(Number(Plasmoid.configuration.refreshInterval)) ? Math.max(0, Number(Plasmoid.configuration.refreshInterval)) : 300
    property bool includeStatus: Plasmoid.configuration.includeStatus
    property bool refreshOnOpen: Plasmoid.configuration.refreshOnOpen === true
    property bool showPopupPace: Plasmoid.configuration.showPopupPace !== false
    property bool showPopupCredits: Plasmoid.configuration.showPopupCredits !== false
    property bool showPopupProviderDetails: Plasmoid.configuration.showPopupProviderDetails !== false
    property bool privacyMode: Plasmoid.configuration.privacyMode === true
    readonly property var presentedProviders: providerPresentations(providers)
    readonly property var presentedOverviewProviders: providerPresentations(overviewProviderItems)
    readonly property var presentedSessions: sessions.map(function(item) {
        return PrivacyPresentation.session(item, privacyMode)
    })
    readonly property var presentedProviderData: providerPresentation(selectedProviderData)
    property bool costUsageEnabled: Plasmoid.configuration.costUsageEnabled !== false
    property int costHistoryDays: isFinite(Number(Plasmoid.configuration.costHistoryDays)) ? Math.max(1, Math.min(365, Number(Plasmoid.configuration.costHistoryDays))) : 30
    // The cost payload already carries per-day tokens next to per-day cost, so
    // switching the plotted metric never needs a second CLI call.
    property string costHistoryMetric: safeCostHistoryMetric(Plasmoid.configuration.costHistoryMetric)
    readonly property bool costHistoryShowsTokens: costHistoryMetric === "tokens"
    // Resolved once: a .pragma library module cannot reach Qt.locale().
    readonly property var costNumberFormat: CostPresentation.numberFormat(
        Qt.locale().groupSeparator, Qt.locale().decimalPoint)
    property bool usageBarsShowUsed: Plasmoid.configuration.usageBarsShowUsed !== false
    property bool showQuotaWarningMarkers: Plasmoid.configuration.showQuotaWarningMarkers !== false
    readonly property int quotaWarningPercent: QuotaThresholds.warningPercent(
        Plasmoid.configuration.quotaWarningPercent)
    // Derived from the warning step so a critical threshold configured below it
    // can never make the "major" level unreachable.
    readonly property int quotaCriticalPercent: QuotaThresholds.criticalPercent(
        quotaWarningPercent,
        Plasmoid.configuration.quotaCriticalPercent)
    property bool enableNotifications: Plasmoid.configuration.enableNotifications !== false
    property bool notifyStatusIncidents: Plasmoid.configuration.notifyStatusIncidents !== false
    property bool notifyQuotaWarnings: Plasmoid.configuration.notifyQuotaWarnings !== false
    property bool notifyPredictivePaceWarnings: Plasmoid.configuration.notifyPredictivePaceWarnings === true
    property bool notifyLimitResets: Plasmoid.configuration.notifyLimitResets === true
    property bool updateNotificationsEnabled: Plasmoid.configuration.updateNotificationsEnabled !== false
    property string menuBarDisplayMode: safeMenuBarDisplayMode(Plasmoid.configuration.menuBarDisplayMode)
    property bool showPopupTabLabels: Plasmoid.configuration.showPopupTabLabels !== false
    property string providerOrderRaw: Plasmoid.configuration.providerOrder || ""
    property string panelProviderIDsRaw: Plasmoid.configuration.panelProviderIDs || ""
    property string panelElementOrderRaw: Plasmoid.configuration.panelElementOrder || ""
    readonly property bool minimalPanel: Plasmoid.configuration.panelStyle === "minimal"
    readonly property string panelQuotaLane: PanelDisplay.safeLane(Plasmoid.configuration.panelQuotaLane)
    readonly property var panelVisibilityRules: PanelRules.normalizedRules(Plasmoid.configuration.panelVisibilityRules)
    property bool resetTimesShowAbsolute: Plasmoid.configuration.resetTimesShowAbsolute === true
    property bool showProviderChangelogs: Plasmoid.configuration.showProviderChangelogs === true
    property bool autoSelectProvider: Plasmoid.configuration.autoSelectProvider === true
    property string overviewProviderIDsRaw: Plasmoid.configuration.overviewProviderIDs || ""
    readonly property int maxOverviewProviders: 3
    property int providerConfigRevision: boundedConfigRevision(Plasmoid.configuration.providerConfigRevision)
    property var providers: []
    property var providerDisplayNames: ({})
    property string errorText: ""
    property string lastUpdatedText: ""
    property bool usageLifecycleInitialized: false
    readonly property string usageIdentityContext: JSON.stringify([
        commandPath, provider, source, providerConfigRevision])
    property bool loading: false
    // Reset labels and run-out durations keep moving even when automatic CLI
    // refresh is disabled. The usage timestamp anchors forecast durations; cost
    // enrichment must not restart that countdown.
    property double panelClockMs: Date.now()
    property double usageSnapshotReceivedAtMs: panelClockMs
    property double usageLastRefreshAttemptAtMs: -1
    property double usageLastCompletedAtMs: -1
    readonly property int panelClockIntervalMs: 60000
    property string commandSource: buildCommand()
    property string providerConfigCommandSource: buildProviderConfigCommand()
    property string providerConfigWatchCommand: buildProviderConfigWatchCommand()
    // The watch poll this instance currently follows. A reply under any other
    // source name belongs to a retired command and must be disconnected, not
    // left running on every interval tick.
    property string connectedProviderConfigWatchCommand: ""
    property string providerConfigStamp: ""
    property var providerRosterCache: null
    property bool usageRefreshScheduled: false
    readonly property int providerConfigWatchIntervalMs: 60000
    property int commandRunSerial: 0
    property bool costLifecycleInitialized: false
    property var activeCommandDescriptors: ({})
    readonly property int defaultCommandTimeoutMs: 120000
    readonly property int maximumExtraRateWindows: Normalizer.maximumExtraRateWindows
    readonly property int maximumProviderSnapshots: Normalizer.maximumProviderSnapshots
    readonly property int maximumAccountSnapshots: Normalizer.maximumAccountSnapshots
    readonly property int maximumCostSnapshots: Normalizer.maximumCostSnapshots
    readonly property int maximumCostHistoryPoints: Normalizer.maximumCostHistoryPoints
    readonly property int maximumCostHistoryScanItems: Normalizer.maximumCostHistoryScanItems
    readonly property int maximumModelBreakdownsPerDay: Normalizer.maximumModelBreakdownsPerDay
    readonly property int maximumConcurrentProviderFallbackCommands: 8
    property var providerFallbackState: null
    property string costCommandSource: buildCostCommand()
    readonly property bool costLoading: CommandLedger.hasKind(activeCommandDescriptors, "cost")
    readonly property int costAutoRefreshIntervalMs: CostRefreshPolicy.automaticRefreshIntervalMs
    property double lastCostRefreshAttemptAt: -1
    property var tokenCosts: ({})
    // The cost command source that produced tokenCosts. Costs belong to the
    // context that fetched them; a source change clears the map, and readers
    // additionally refuse snapshots from any other source.
    property string tokenCostsContext: ""
    property var costTrustNoticeStates: ({})
    property string costErrorText: ""
    property string sessionsCommandSource: buildSessionsCommand()
    property var sessions: []
    property string sessionsErrorText: ""
    property string sessionsLastUpdatedText: ""
    property bool sessionsLoading: false
    property double sessionsLastCompletedAtMs: -1
    property string sessionsLoadedCommandSource: ""
    readonly property int sessionsStaleAfterMs: SessionRefreshPolicy.staleAfterMs(refreshIntervalSec)
    readonly property int maximumSessions: Normalizer.maximumSessions
    readonly property int sessionsCommandTimeoutMs: 60000
    property string selectedProviderID: ""
    // Provider meter currently under the panel pointer. The plasmoid tooltip
    // narrows to this provider while it is set, so hovering one panel icon
    // reports only that provider instead of the whole roster.
    property string hoveredPanelProviderID: ""
    property string selectedGlobalView: "overview"
    property bool selectionInitialized: false
    property var selectedAccounts: ({})
    property var accountOptions: ({})
    property var accountErrors: ({})
    readonly property int accountCommandTimeoutMs: 60000
    readonly property int notificationCommandTimeoutMs: 10000
    property var notificationMemo: ({})
    property var notificationRefreshPending: ({})
    property bool notificationsPrimed: false
    property string lastNotifiedUpdateVersion: Plasmoid.configuration.lastNotifiedUpdateVersion || ""
    readonly property bool verticalFormFactor: Plasmoid.formFactor === PlasmaCore.Types.Vertical
    readonly property var overviewProviderItems: overviewProviders()
    readonly property bool globalNavigationAvailable: provider.length === 0
    readonly property bool overviewAvailable: globalNavigationAvailable && providers.length > 1 && overviewProviderItems.length > 0
    readonly property bool spendAvailable: globalNavigationAvailable && costUsageEnabled
    readonly property bool sessionsAvailable: globalNavigationAvailable
    readonly property int selectedProviderIndex: providerIndexForID(selectedProviderID)
    readonly property bool globalViewSelected: selectionInitialized && selectedProviderID.length === 0
    readonly property bool overviewSelected: overviewAvailable && globalViewSelected && selectedGlobalView === "overview"
    readonly property bool spendSelected: spendAvailable && globalViewSelected && selectedGlobalView === "spend"
    readonly property bool sessionsSelected: sessionsAvailable && globalViewSelected && selectedGlobalView === "sessions"
    readonly property bool providerUsageFeedbackVisible: !spendSelected && !sessionsSelected
    readonly property var selectedProviderData: providers.length > 0 && selectedProviderIndex >= 0
        ? providers[Math.min(selectedProviderIndex, providers.length - 1)]
        : null
    readonly property real roundedSurfaceRadius: Kirigami.Units.cornerRadius
        + Kirigami.Units.smallSpacing
    // Radius for a small surface drawn inside a roundedSurfaceRadius container.
    // Concentric rounding wants the inner radius reduced by the inset between
    // the two edges, and popup surfaces inset their content by smallSpacing, so
    // this is roundedSurfaceRadius minus that inset by construction.
    readonly property real nestedSurfaceRadius: Kirigami.Units.cornerRadius
    // Two-step de-emphasis scale for popup text. A supporting label uses the
    // secondary step and the value it annotates uses the stronger step, so a
    // label/value pair keeps its hierarchy without inventing a new opacity per
    // section. 0.7 is the lowest step where Kirigami.Theme.textColor still
    // clears WCAG AA 4.5:1 against the Breeze Light popup background.
    readonly property real secondaryTextOpacity: 0.7
    readonly property real valueTextOpacity: 0.85
    // Shared track height for the credits, cost, and usage meters that stack in
    // one provider detail view. Derived from gridUnit so it follows the user
    // font size instead of pinning a device pixel count.
    readonly property real meterTrackHeight: Math.round(Kirigami.Units.gridUnit * 0.4)
    // Thinner track for meters that sit inside a scannable list row (overview
    // rows, cost history rows) instead of leading a detail section. Keeping the
    // two scales apart is what makes the primary meters read as primary.
    readonly property real compactMeterTrackHeight: Math.round(Kirigami.Units.gridUnit * 0.28)

    onUsageIdentityContextChanged: invalidateUsageData()
    onCommandSourceChanged: scheduleUsageRefresh()
    onProviderConfigWatchCommandChanged: reconnectProviderConfigWatcher()
    onProviderOrderRawChanged: providers = ProviderOrder.orderedItems(
        providers, providerOrderRaw)
    onProviderConfigCommandSourceChanged: {
        invalidateProviderRosterCache()
        scheduleUsageRefresh()
    }
    onCostHistoryDaysChanged: applyTokenCosts()
    onCostCommandSourceChanged: {
        if (costLifecycleInitialized) {
            // Retire live cost runs synchronously so a late reply from the
            // previous source cannot reseed the map: the ledger drops retired
            // replies before parsing. The map itself is retained and
            // reprojected at once, because the source can settle after the
            // range handler already ran; readers still refuse snapshots from
            // any other source until the refetch lands.
            retireUsageCommandKind("cost")
            applyTokenCosts()
            Qt.callLater(function() { root.refreshCost(true) })
        }
    }
    onProviderConfigRevisionChanged: {
        invalidateProviderRosterCache()
        scheduleUsageRefresh()
    }
    onSessionsCommandSourceChanged: {
        if (retireUsageCommandKind("sessions") > 0) {
            sessionsLoading = false
        }
        if (expanded && sessionsSelected) {
            Qt.callLater(refreshSessionsIfStale)
        } else {
            scheduleSessionsRefreshCheck()
        }
    }
    onSessionsStaleAfterMsChanged: {
        Qt.callLater(refreshSessionsIfStale)
    }
    onExpandedChanged: {
        if (root.expanded) {
            Qt.callLater(refreshUsageOnOpen)
            Qt.callLater(refreshSessionsIfStale)
        } else {
            scheduleSessionsRefreshCheck()
        }
    }
    onSessionsSelectedChanged: {
        if (sessionsSelected && expanded) {
            Qt.callLater(refreshSessionsIfStale)
        } else {
            scheduleSessionsRefreshCheck()
        }
    }
    onAutoSelectProviderChanged: updateSelectedProvider()
    onOverviewProviderIDsRawChanged: updateSelectedProvider()
    onOverviewAvailableChanged: reconcileGlobalViewAvailability()
    onSpendAvailableChanged: reconcileGlobalViewAvailability()
    onSessionsAvailableChanged: reconcileGlobalViewAvailability()
    onEnableNotificationsChanged: resetNotificationMemo()
    onNotifyStatusIncidentsChanged: resetNotificationMemo()
    onNotifyQuotaWarningsChanged: resetNotificationMemo()
    onNotifyPredictivePaceWarningsChanged: resetNotificationMemo()
    // The memo stores the level each row was last observed at. Keeping it across
    // a threshold change would suppress a warning the new lower threshold should
    // raise, and leave a row armed at a level the new higher threshold no longer
    // reaches.
    onQuotaWarningPercentChanged: resetNotificationMemo()
    onQuotaCriticalPercentChanged: resetNotificationMemo()
    onNotifyLimitResetsChanged: resetNotificationMemo()
    onProvidersChanged: {
        if (providers.length === 0) {
            hoveredPanelProviderID = ""
            updateSelectedProvider()
            resetNotificationMemo()
            return
        }
        if (hoveredPanelProviderID.length > 0 && providerIndexForID(hoveredPanelProviderID) < 0) {
            hoveredPanelProviderID = ""
        }
        updateSelectedProvider()
        Qt.callLater(processNotifications)
    }

    Component.onCompleted: {
        usageLifecycleInitialized = true
        costLifecycleInitialized = true
        reconnectProviderConfigWatcher()
        refreshNow(false)
        refreshCost(false)
    }

    function buildCommand() {
        if (commandPath.length === 0) {
            return ""
        }

        var parts = [
            shellQuote(commandPath),
            "usage",
            "--format",
            "json",
            "--json-only"
        ]

        if (provider.length > 0) {
            parts.push("--provider")
            parts.push(shellQuote(provider))
            var selectedAccount = selectedAccountForProvider(provider)
            if (selectedAccount.length > 0) {
                parts.push("--account")
                parts.push(shellQuote(selectedAccount))
            }
        }

        if (source.length > 0) {
            parts.push("--source")
            parts.push(shellQuote(source))
        }

        if (includeStatus) {
            parts.push("--status")
        }

        return parts.join(" ")
    }

    function buildProviderAccountsCommand(providerID) {
        if (commandPath.length === 0) {
            return ""
        }

        var parts = [
            shellQuote(commandPath),
            "usage",
            "--provider",
            shellQuote(providerCliArgument(providerID)),
            "--all-accounts",
            "--format",
            "json",
            "--json-only"
        ]

        if (source.length > 0) {
            parts.push("--source")
            parts.push(shellQuote(source))
        }

        if (includeStatus) {
            parts.push("--status")
        }

        return parts.join(" ")
    }

    function buildProviderConfigCommand() {
        if (commandPath.length === 0) {
            return ""
        }

        return [
            shellQuote(commandPath),
            "config",
            "providers",
            "--format",
            "json",
            "--json-only"
        ].join(" ")
    }

    function buildProviderConfigWatchCommand() {
        var script = [
            "config=${CODEXBAR_CONFIG:-};",
            "case \"$config\" in '~/'*) config=\"$HOME/${config#\\~/}\";; esac;",
            "if [ -z \"$config\" ]; then",
            "xdg=${XDG_CONFIG_HOME:-};",
            "case \"$xdg\" in '~/'*) xdg=\"$HOME/${xdg#\\~/}\";; esac;",
            "case \"$xdg\" in",
            "/*) config=\"$xdg/codexbar/config.json\";;",
            "*) config=\"$HOME/.config/codexbar/config.json\"; if [ ! -e \"$config\" ] && [ -e \"$HOME/.codexbar/config.json\" ]; then config=\"$HOME/.codexbar/config.json\"; fi;;",
            "esac;",
            "fi;",
            "if [ -r \"$config\" ]; then cksum \"$config\"; else printf missing; fi"
        ].join(" ")
        return ["sh", "-c", shellQuote(script)].join(" ")
    }

    function buildProviderUsageCommand(providerID) {
        var parts = [
            shellQuote(commandPath),
            "usage",
            "--provider",
            shellQuote(providerCliArgument(providerID)),
            "--format",
            "json",
            "--json-only"
        ]

        if (source.length > 0) {
            parts.push("--source")
            parts.push(shellQuote(source))
        }

        var selectedAccount = selectedAccountForProvider(providerID)
        if (selectedAccount.length > 0) {
            parts.push("--account")
            parts.push(shellQuote(selectedAccount))
        }

        if (includeStatus) {
            parts.push("--status")
        }

        return parts.join(" ")
    }

    function buildCostCommand() {
        if (commandPath.length === 0) {
            return ""
        }
        if (!costUsageEnabled) {
            return ""
        }

        var parts = [
            shellQuote(commandPath),
            "cost",
            "--format",
            "json",
            "--json-only",
            "--days",
            String(costHistoryDays)
        ]

        if (provider.length > 0) {
            parts.push("--provider")
            parts.push(shellQuote(provider))
        }

        return parts.join(" ")
    }

    function buildSessionsCommand() {
        if (commandPath.length === 0) {
            return ""
        }
        return [shellQuote(commandPath), "sessions", "--json-v2"].join(" ")
    }

    function shellQuote(value) {
        return Guards.shellQuote(value)
    }

    function safeMenuBarDisplayMode(value) {
        return PanelDisplay.safeMode(value)
    }

    function safeCostHistoryMetric(value) {
        return String(value || "cost") === "tokens" ? "tokens" : "cost"
    }

    function panelElementOrder() {
        return PanelElements.normalizedOrder(panelElementOrderRaw)
    }

    function boundedConfigRevision(value) {
        var revision = Number(value)
        if (!isFinite(revision)) {
            return 0
        }
        return Math.max(0, Math.min(2147480000, Math.floor(revision)))
    }

    function boundedCliMessage(value) {
        return SafeText.cliMessage(SafeText.stripLoaderDiagnostics(value), SafeText.maximumCliMessageLength)
    }

    function isCliRecord(value) {
        return Normalizer.isCliRecord(value)
    }

    function normalizedProviderID(value) {
        return Normalizer.normalizedProviderID(value)
    }

    function hasOwnKey(item, key) {
        return Guards.hasOwnKey(item, key)
    }

    function isUnsafeObjectKey(key) {
        return Guards.isUnsafeObjectKey(key)
    }

    function providerMapKey(providerID) {
        return Normalizer.providerSnapshotKey(providerID)
    }

    function commandWithRunNonce(command) {
        if (command.length === 0) {
            return ""
        }
        commandRunSerial += 1
        return CommandLedger.withRunNonce(command, commandRunSerial)
    }

    function connectUsageCommand(sourceName, descriptor) {
        if (sourceName.length === 0) {
            return
        }

        activeCommandDescriptors = CommandLedger.opened(
            activeCommandDescriptors, sourceName, descriptor)
        usageSource.connectSource(sourceName)
    }

    function buildCommandDescriptor(kind, providerID, timeoutMs) {
        return CommandLedger.descriptor(
            kind, providerID, Date.now(), timeoutMs, defaultCommandTimeoutMs)
    }

    function buildCostCommandDescriptor() {
        var descriptor = buildCommandDescriptor("cost", "")
        descriptor.costHistoryDays = costHistoryDays
        return descriptor
    }

    function providerRosterContext() {
        return {
            commandSource: providerConfigCommandSource,
            revision: providerConfigRevision,
            stamp: providerConfigStamp
        }
    }

    function buildProviderConfigCommandDescriptor() {
        var descriptor = buildCommandDescriptor("providerConfig", "")
        descriptor.providerRosterContext = providerRosterContext()
        return descriptor
    }

    function finishUsageCommandSource(sourceName) {
        if (sourceName.length === 0) {
            return
        }

        usageSource.disconnectSource(sourceName)
        activeCommandDescriptors = CommandLedger.closed(activeCommandDescriptors, sourceName)
    }

    function connectNotificationCommand(sourceName) {
        if (sourceName.length === 0) {
            return
        }
        var descriptor = buildCommandDescriptor(
            "notification", "", notificationCommandTimeoutMs)
        activeCommandDescriptors = CommandLedger.opened(
            activeCommandDescriptors, sourceName, descriptor)
        notificationSource.connectSource(sourceName)
    }

    function finishNotificationCommandSource(sourceName) {
        if (sourceName.length === 0) {
            return
        }
        notificationSource.disconnectSource(sourceName)
        activeCommandDescriptors = CommandLedger.closed(activeCommandDescriptors, sourceName)
    }

    // Retiring by kind is what makes a late reply harmless: the source name
    // leaves the ledger, so routing no longer recognises it.
    function retireUsageCommandKind(kind) {
        var sourceNames = CommandLedger.sourcesOfKind(activeCommandDescriptors, kind)
        for (var i = 0; i < sourceNames.length; i++) {
            finishUsageCommandSource(sourceNames[i])
        }
        return sourceNames.length
    }

    function scheduleUsageRefresh() {
        if (usageRefreshScheduled) {
            return
        }
        usageRefreshScheduled = true
        Qt.callLater(function() {
            if (!root.usageRefreshScheduled) {
                return
            }
            root.usageRefreshScheduled = false
            root.refreshNow(false)
        })
    }

    function refreshNow(bypassProviderRosterCache) {
        usageRefreshScheduled = false
        retireUsageCommands()
        retireStaleAccountCommands()

        if (commandSource.length === 0) {
            failUsageRefresh(i18n("Set the codexbar command path in widget settings."))
            return
        }

        usageLastRefreshAttemptAtMs = Date.now()
        loading = true
        errorText = ""
        if (canUseProviderFallback()) {
            startProviderFallback(bypassProviderRosterCache === true)
            return
        }
        connectUsageCommand(
            commandWithRunNonce(commandSource),
            buildCommandDescriptor("usage", ""))
    }

    function markUsageSnapshotReceived() {
        var nowMs = Date.now()
        usageLastCompletedAtMs = nowMs
        usageSnapshotReceivedAtMs = nowMs
        panelClockMs = nowMs
    }

    function usageCacheContext() {
        if (providerConfigStamp.length === 0 || commandSource.length === 0) {
            return ""
        }
        var accounts = Object.keys(selectedAccounts).sort().map(function(key) {
            return [key, root.selectedAccounts[key]]
        })
        return Qt.md5(JSON.stringify([usageIdentityContext, accounts, providerConfigStamp]))
    }

    function invalidateUsageData(providerID) {
        if (!usageLifecycleInitialized) {
            return
        }
        retireUsageCommands()
        providers = providerID ? providers.map(function(item) {
            return item.provider === providerID ? root.normalizeProvider({ provider: providerID }) : item
        }) : []
        // A single-provider reset must not discard healthy providers'
        // persisted quotas; rewrite the disk cache from what remains.
        var context = usageCacheContext()
        if (context.length > 0) {
            Plasmoid.configuration.usageCache = UsageCache.encode(providers, context, Date.now())
        } else if (!providerID) {
            Plasmoid.configuration.usageCache = ""
        }
        if (!providerID) {
            retireUsageCommandKind("account")
            accountOptions = ({})
            accountErrors = ({})
        }
        usageLastCompletedAtMs = -1
        lastUpdatedText = ""
        loading = false
    }

    function restoreUsageCache() {
        var context = usageCacheContext()
        if (context.length === 0) {
            return
        }
        var nowMs = Date.now()
        var restored = UsageCache.decode(Plasmoid.configuration.usageCache, context, nowMs)
        var cachedProviders = restored.map(function(payload) {
            var item = root.normalizeProvider(payload)
            item.lastGoodAtMs = Date.parse(payload.usage.updatedAt)
            item.usageStale = true
            item.tokenCost = null
            return item
        })
        var merged = UsageCache.restore(cachedProviders, providers, nowMs, selectedAccounts)
        if (merged.length === 0) {
            Plasmoid.configuration.usageCache = UsageCache.encode(providers, context, nowMs)
            return
        }
        providers = ProviderOrder.orderedItems(merged, providerOrderRaw)
        Plasmoid.configuration.usageCache = UsageCache.encode(providers, context, nowMs)
        if (providers.some(function(item) { return item.usageStale === true })) {
            lastUpdatedText = i18n("Showing last known usage")
        }
    }

    function commitUsageSnapshot(items) {
        var nowMs = Date.now()
        var nextProviders = UsageCache.reconcile(providers, items, nowMs)
        markNotificationProvidersFresh(nextProviders)
        var hasFreshUsage = nextProviders.some(function(item) { return !item.usageStale && item.error.length === 0 })
        if (hasFreshUsage) {
            markUsageSnapshotReceived()
        }
        providers = nextProviders
        lastUpdatedText = nextProviders.some(function(item) { return item.usageStale === true })
            ? i18n("Showing last known usage")
            : hasFreshUsage ? i18n("Updated %1", Qt.formatDateTime(new Date(nowMs), "hh:mm")) : ""
        var context = usageCacheContext()
        if (context.length > 0) {
            Plasmoid.configuration.usageCache = UsageCache.encode(nextProviders, context, nowMs)
        }
    }

    function failUsageRefresh(message) {
        var nowMs = Date.now()
        var failures = providers.map(function(item) {
            return root.normalizeProvider(root.providerErrorPayload(item.provider, message))
        })
        providers = UsageCache.reconcile(providers, failures, nowMs)
        lastUpdatedText = providers.some(function(item) { return item.usageStale === true })
            ? i18n("Showing last known usage") : ""
        var context = usageCacheContext()
        if (context.length > 0) {
            Plasmoid.configuration.usageCache = UsageCache.encode(providers, context, nowMs)
        }
        panelClockMs = nowMs
        errorText = message
        loading = false
    }

    function expireStaleUsage(nowMs) {
        var expired = UsageCache.expiredProviderIDs(providers, nowMs)
        if (expired.length === 0) {
            return
        }
        providers = providers.map(function(item) {
            if (expired.indexOf(item.provider) < 0) {
                return item
            }
            var replacement = UsageCache.withCurrentStatus(root.normalizeProvider(root.providerErrorPayload(item.provider,
                item.error || i18n("Cached usage has expired. Refresh to try again."))), item)
            // withCurrentStatus copies a fresh error snapshot, which carries
            // no measurement contract; expired quotas keep no measurement.
            replacement.lastGoodAtMs = 0
            replacement.usageStale = false
            return replacement
        })
        if (!providers.some(function(item) { return item.usageStale === true })) {
            lastUpdatedText = ""
        }
        var context = usageCacheContext()
        if (context.length > 0) {
            Plasmoid.configuration.usageCache = UsageCache.encode(providers, context, nowMs)
        }
    }

    function lastGoodUsageText(item) {
        var ageSeconds = Math.max(0, (panelClockMs - item.lastGoodAtMs) / 1000)
        return ageSeconds < 60 ? i18n("Last known usage, just now")
            : i18n("Last known usage, %1 ago", paceEtaText(ageSeconds))
    }

    function providerUsageTimestamp(item) {
        if (!item || !(item.lastGoodAtMs > 0)) {
            return ""
        }
        return item.usageStale === true ? lastGoodUsageText(item)
            : i18n("Updated %1", Qt.formatDateTime(new Date(item.lastGoodAtMs), "hh:mm"))
    }

    function refreshUsageOnOpen() {
        if (PopupRefreshPolicy.shouldRefresh({
            enabled: refreshOnOpen,
            visible: expanded,
            loading: loading,
            scheduled: usageRefreshScheduled,
            commandSource: commandSource,
            lastAttemptAtMs: usageLastRefreshAttemptAtMs,
            lastCompletedAtMs: usageLastCompletedAtMs,
            nowMs: Date.now(),
            refreshIntervalSeconds: refreshIntervalSec
        })) {
            refreshNow(false)
        }
    }

    function retireUsageCommands() {
        retireUsageCommandKind("usage")
        retireUsageCommandKind("providerConfig")
        retireUsageCommandKind("providerFallback")
        // Account loads are user-triggered; keep them alive across usage refreshes
        // so their replies can still populate the account picker.
        providerFallbackState = null
    }

    function reconnectProviderConfigWatcher() {
        if (connectedProviderConfigWatchCommand.length > 0
                && connectedProviderConfigWatchCommand !== providerConfigWatchCommand) {
            providerConfigWatcher.disconnectSource(connectedProviderConfigWatchCommand)
        }
        // Connecting to a shared source can synchronously deliver cached data.
        connectedProviderConfigWatchCommand = providerConfigWatchCommand
        if (providerConfigWatchCommand.length > 0) {
            providerConfigWatcher.connectSource(providerConfigWatchCommand)
        }
    }

    function handleProviderConfigWatch(stdoutText) {
        var stamp = stdoutText.trim()
        if (stamp.length === 0) {
            return
        }
        if (providerConfigStamp.length === 0) {
            providerConfigStamp = stamp
            invalidateProviderRosterCache()
            restoreUsageCache()
            return
        }
        if (stamp === providerConfigStamp) {
            return
        }
        providerConfigStamp = stamp
        invalidateProviderRosterCache()
        invalidateUsageData()
        scheduleUsageRefresh()
    }

    function refreshCost(force) {
        var nowMs = Date.now()
        var action = CostRefreshPolicy.refreshAction(
            costCommandSource.length > 0,
            costLoading,
            force === true,
            lastCostRefreshAttemptAt,
            nowMs)
        if (action === CostRefreshPolicy.clearAction) {
            retireUsageCommandKind("cost")
            tokenCosts = ({})
            tokenCostsContext = ""
            costErrorText = ""
            applyTokenCosts()
            return false
        }
        if (action !== CostRefreshPolicy.startAction) {
            return false
        }

        retireUsageCommandKind("cost")
        lastCostRefreshAttemptAt = nowMs
        costErrorText = ""
        connectUsageCommand(
            commandWithRunNonce(costCommandSource),
            buildCostCommandDescriptor())
        return true
    }

    function requestSessionsRefresh(force) {
        var action = SessionRefreshPolicy.refreshAction({
            commandSource: sessionsCommandSource,
            loadedCommandSource: sessionsLoadedCommandSource,
            loading: sessionsLoading,
            visible: expanded && sessionsSelected,
            force: force === true,
            lastCompletedAtMs: sessionsLastCompletedAtMs,
            nowMs: Date.now(),
            staleAfterMs: sessionsStaleAfterMs
        })
        if (action === SessionRefreshPolicy.keepAction) {
            return false
        }
        if (action === SessionRefreshPolicy.missingCommandAction) {
            sessionsLoading = false
            sessionsErrorText = i18n("Set the codexbar command path in widget settings.")
            return false
        }

        retireUsageCommandKind("sessions")
        sessionsLoading = true
        sessionsErrorText = ""
        connectUsageCommand(
            commandWithRunNonce(sessionsCommandSource),
            buildCommandDescriptor("sessions", "", sessionsCommandTimeoutMs))
        return true
    }

    function refreshSessionsIfStale() {
        var started = requestSessionsRefresh(false)
        scheduleSessionsRefreshCheck()
        return started
    }

    function refreshSessions() {
        var started = requestSessionsRefresh(true)
        scheduleSessionsRefreshCheck()
        return started
    }

    function scheduleSessionsRefreshCheck() {
        sessionsRefreshTimer.stop()
        var delayMs = SessionRefreshPolicy.nextCheckDelay({
            commandSource: sessionsCommandSource,
            loadedCommandSource: sessionsLoadedCommandSource,
            loading: sessionsLoading,
            visible: expanded && sessionsSelected,
            force: false,
            lastCompletedAtMs: sessionsLastCompletedAtMs,
            nowMs: Date.now(),
            staleAfterMs: sessionsStaleAfterMs
        })
        if (delayMs <= 0) {
            return
        }
        sessionsRefreshTimer.interval = delayMs
        sessionsRefreshTimer.start()
    }

    function parseOutput(stdoutText, stderrText) {
        var trimmed = stdoutText.trim()
        if (trimmed.length === 0) {
            if (canUseProviderFallback()) {
                startProviderFallback()
                return
            }
            failUsageRefresh(stderrText.trim().length > 0 ? boundedCliMessage(stderrText) : i18n("codexbar did not return JSON."))
            return
        }

        var payload
        try {
            payload = JSON.parse(trimmed)
        } catch (error) {
            failUsageRefresh(i18n("Could not parse codexbar JSON: %1", error.message))
            return
        }

        var items = Array.isArray(payload) ? payload : [payload]
        var nextProviders = []
        var itemLimit = Math.min(items.length, maximumProviderSnapshots)
        for (var i = 0; i < itemLimit; i++) {
            // A malformed provider must not abort the whole refresh or leave
            // loading set: skip it and keep the healthy providers. The id
            // screen stays inside the guard so a throwing identity read drops
            // only its own provider instead of stranding the whole run.
            try {
                if (!isCliRecord(items[i]) || normalizedProviderID(items[i].provider).length === 0) {
                    continue
                }
                nextProviders.push(normalizeProvider(items[i]))
            } catch (providerError) {
                continue
            }
        }

        nextProviders = ProviderOrder.orderedItems(
            Normalizer.dedupeProviderSnapshots(nextProviders),
            providerOrderRaw)

        if (nextProviders.length === 0) {
            failUsageRefresh(stderrText.trim().length > 0 ? boundedCliMessage(stderrText) : i18n("codexbar did not return provider data."))
            return
        }
        commitUsageSnapshot(nextProviders)
        errorText = ""
        loading = false
    }

    function hasSelectedAccountOverrides() {
        for (var providerID in selectedAccounts) {
            if (hasOwnKey(selectedAccounts, providerID)
                && String(selectedAccounts[providerID] || "").length > 0) {
                return true
            }
        }
        return false
    }

    function canUseProviderFallback() {
        return source.length === 0 || hasSelectedAccountOverrides()
    }

    function startProviderFallback(bypassProviderRosterCache) {
        retireUsageCommandKind("usage")
        if (provider.length > 0) {
            startProviderFallbackForProviders([providerKey(provider)])
            return
        }

        if (bypassProviderRosterCache !== true) {
            var cachedProviderIDs = ProviderRosterCache.read(
                providerRosterCache, providerRosterContext())
            if (cachedProviderIDs !== null) {
                startProviderFallbackForProviders(cachedProviderIDs)
                return
            }
        }

        if (providerConfigCommandSource.length === 0) {
            failUsageRefresh(i18n("codexbar did not return JSON."))
            return
        }

        connectUsageCommand(
            commandWithRunNonce(providerConfigCommandSource),
            buildProviderConfigCommandDescriptor())
    }

    function invalidateProviderRosterCache() {
        providerRosterCache = null
    }

    function parseProviderConfigOutput(descriptor, stdoutText, stderrText) {
        // Config reads are asynchronous. A reply from an older command,
        // revision, or checksum must not seed a cache for the current context.
        if (!descriptor || !ProviderRosterCache.responseContextsMatch(
                descriptor.providerRosterContext, providerRosterContext())) {
            scheduleUsageRefresh()
            return
        }
        var trimmed = stdoutText.trim()
        if (trimmed.length === 0) {
            failUsageRefresh(stderrText.trim().length > 0 ? boundedCliMessage(stderrText) : i18n("Could not load CodexBar provider configuration."))
            return
        }

        var payload
        try {
            payload = JSON.parse(trimmed)
        } catch (error) {
            failUsageRefresh(i18n("Could not parse CodexBar provider configuration: %1", error.message))
            return
        }

        var entries = Normalizer.normalizeProviderConfigEntries(payload)
        if (entries === null) {
            failUsageRefresh(i18n("Could not load CodexBar provider configuration."))
            return
        }
        providerDisplayNames = entries.displayNames
        providerRosterCache = ProviderRosterCache.remember(
            entries.providerIDs, descriptor.providerRosterContext)
        startProviderFallbackForProviders(entries.providerIDs)
    }

    function startProviderFallbackForProviders(providerIDs) {
        retireUsageCommandKind("providerFallback")
        providerFallbackState = null

        var orderedProviderIDs = ProviderOrder.orderedItems(
            providerIDs, providerOrderRaw)
        var requests = []
        var providerLimit = Math.min(orderedProviderIDs.length, maximumProviderSnapshots)
        for (var i = 0; i < providerLimit; i++) {
            var providerID = normalizedProviderID(String(orderedProviderIDs[i] || ""))
            if (providerID.length === 0) {
                continue
            }
            var baseCommand = buildProviderUsageCommand(providerID)
            requests.push({
                sourceName: commandWithRunNonce(baseCommand),
                providerID: providerID
            })
        }

        var transition = ProviderFallbackQueue.begin(requests, {
            maximumConcurrent: maximumConcurrentProviderFallbackCommands,
            maximumSnapshots: maximumProviderSnapshots
        })
        if (transition.finished) {
            providerFallbackState = null
            invalidateUsageData()
            errorText = i18n("No enabled CodexBar providers.")
            loading = false
            return
        }
        applyProviderFallbackTransition(transition)
    }

    function applyProviderFallbackTransition(transition) {
        providerFallbackState = transition.state
        var sourcesToStart = transition.sourcesToStart
        for (var i = 0; i < sourcesToStart.length; i++) {
            var request = sourcesToStart[i]
            connectUsageCommand(
                request.sourceName,
                buildCommandDescriptor("providerFallback", request.providerID))
        }
        if (transition.finished) {
            finishProviderFallback(transition.orderedItems)
        }
    }

    function completeProviderFallbackSlot(sourceName, item) {
        var transition = ProviderFallbackQueue.complete(providerFallbackState, {
            sourceName: sourceName,
            item: item
        })
        applyProviderFallbackTransition(transition)
    }

    function parseProviderFallbackOutput(sourceName, providerID, stdoutText, stderrText) {
        providerID = normalizedProviderID(providerID)
        if (providerID.length === 0) {
            finishUsageCommandSource(sourceName)
            return
        }
        finishUsageCommandSource(sourceName)

        // The ledger entry is closed above, so an unexpected parse failure
        // would strand a fallback queue slot until the whole run retires.
        // The guard only selects the item; the slot is completed exactly once
        // outside it, so a throwing completion can never be retried against
        // already-advanced queue state.
        var completedItem = null
        try {
            var normalizedItems = []
            var trimmed = stdoutText.trim()
            if (trimmed.length === 0) {
                normalizedItems.push(normalizeProvider(providerErrorPayload(
                    providerID,
                    stderrText.trim().length > 0 ? boundedCliMessage(stderrText) : i18n("codexbar did not return JSON."))))
            } else {
                var payload
                var parsedWithoutRecords = false
                try {
                    payload = JSON.parse(trimmed)
                    var items = Array.isArray(payload) ? payload : [payload]
                    var itemLimit = Math.min(items.length, maximumAccountSnapshots)
                    for (var i = 0; i < itemLimit; i++) {
                        if (!isCliRecord(items[i])) {
                            continue
                        }
                        var providerItem = copyObject(items[i])
                        // A provider-scoped command may only update the requested
                        // provider, even if a malformed CLI payload claims another id.
                        providerItem.provider = providerID
                        normalizedItems.push(normalizeProvider(providerItem))
                    }
                    parsedWithoutRecords = normalizedItems.length === 0
                } catch (error) {
                    normalizedItems.push(normalizeProvider(providerErrorPayload(
                        providerID,
                        i18n("Could not parse codexbar JSON: %1", error.message))))
                }
                if (parsedWithoutRecords) {
                    // Valid JSON without CLI records (null, [], scalars) must
                    // degrade to a scoped error row: a null item would silently
                    // drop this provider from the roster while others stay healthy.
                    normalizedItems.push(normalizeProvider(providerErrorPayload(
                        providerID,
                        i18n("codexbar did not return provider data."))))
                }
            }

            var semanticItems = Normalizer.dedupeProviderSnapshots(normalizedItems)
            completedItem = semanticItems.length > 0 ? semanticItems[0] : null
        } catch (error) {
            completedItem = normalizeProvider(providerErrorPayload(
                providerID,
                i18n("Could not parse codexbar JSON: %1", error.message)))
        }
        completeProviderFallbackSlot(sourceName, completedItem)
    }

    function finishProviderFallback(orderedItems) {
        var nextProviders = Array.isArray(orderedItems) ? orderedItems : []
        nextProviders = ProviderOrder.orderedItems(
            Normalizer.dedupeProviderSnapshots(nextProviders),
            providerOrderRaw)

        commitUsageSnapshot(nextProviders)
        errorText = nextProviders.length === 0 ? i18n("codexbar did not return JSON.") : ""
        loading = false
        providerFallbackState = null
        applyTokenCosts()
    }

    function providerErrorPayload(providerID, message) {
        return {
            provider: providerID,
            source: source.length > 0 ? source : "auto",
            error: {
                code: 1,
                kind: "provider",
                message: message
            }
        }
    }

    function loadAccounts(providerID) {
        var normalizedProviderID = providerKey(providerID)
        if (accountLoadingForProvider(normalizedProviderID)) {
            return
        }

        var command = buildProviderAccountsCommand(normalizedProviderID)
        if (command.length === 0) {
            setAccountError(normalizedProviderID, i18n("Set the codexbar command path in widget settings."))
            return
        }

        setAccountError(normalizedProviderID, "")
        var connectedCommand = commandWithRunNonce(command)
        var descriptor = buildCommandDescriptor(
            "account", normalizedProviderID, accountCommandTimeoutMs)
        descriptor.commandSignature = command
        connectUsageCommand(connectedCommand, descriptor)
    }

    function retireStaleAccountCommands() {
        var sourceNames = CommandLedger.sourcesOfKind(activeCommandDescriptors, "account")
        for (var i = 0; i < sourceNames.length; i++) {
            var sourceName = sourceNames[i]
            var descriptor = CommandLedger.find(activeCommandDescriptors, sourceName)
            var decision = AccountRequests.completion(activeCommandDescriptors, sourceName,
                buildProviderAccountsCommand(descriptor.providerID))
            if (decision && !decision.acceptsPayload) {
                finishUsageCommandSource(sourceName)
            }
        }
    }

    function hasPendingCommandTimeouts() {
        return CommandLedger.hasDeadlines(activeCommandDescriptors)
    }

    function hasPendingPeriodicRefreshCommands() {
        return CommandLedger.hasAnyKind(activeCommandDescriptors, [
            "usage",
            "providerConfig",
            "providerFallback"
        ])
    }

    function expireCommands(nowMs) {
        var expired = CommandLedger.expired(activeCommandDescriptors, nowMs)
        for (var i = 0; i < expired.length; i++) {
            handleCommandTimeout(expired[i].sourceName, expired[i].descriptor)
        }
    }

    // The ledger entry already proves the command is the live one for its kind,
    // so the kind alone decides how the timeout is reported.
    function handleCommandTimeout(sourceName, descriptor) {
        if (!descriptor || !CommandLedger.find(activeCommandDescriptors, sourceName)) {
            return
        }

        switch (descriptor.kind) {
        case "usage":
            finishUsageCommandSource(sourceName)
            if (canUseProviderFallback()) {
                startProviderFallback()
                return
            }
            failUsageRefresh(i18n("Loading usage timed out. Try again."))
            return
        case "cost":
            finishUsageCommandSource(sourceName)
            costErrorText = i18n("Loading cost data timed out. Try again.")
            applyTokenCosts()
            return
        case "sessions":
            finishUsageCommandSource(sourceName)
            sessionsLoading = false
            sessionsErrorText = i18n("Loading sessions timed out. Try again.")
            scheduleSessionsRefreshCheck()
            return
        case "providerConfig":
            finishUsageCommandSource(sourceName)
            failUsageRefresh(i18n("Loading provider configuration timed out. Try again."))
            return
        case "account":
            finishUsageCommandSource(sourceName)
            setAccountError(
                descriptor.providerID,
                i18n("Loading accounts timed out. Try again."))
            return
        case "providerFallback":
            parseProviderFallbackOutput(
                sourceName,
                descriptor.providerID,
                "",
                i18n("Loading usage timed out. Try again."))
            return
        case "notification":
            finishNotificationCommandSource(sourceName)
            return
        default:
            finishUsageCommandSource(sourceName)
        }
    }

    function parseProviderAccountsOutput(sourceName, descriptor, stdoutText, stderrText) {
        var decision = AccountRequests.completion(activeCommandDescriptors, sourceName,
            descriptor ? buildProviderAccountsCommand(descriptor.providerID) : "")
        if (!decision) {
            return
        }
        finishUsageCommandSource(sourceName)
        if (!decision.acceptsPayload) {
            return
        }
        var providerID = providerMapKey(decision.providerID)
        if (providerID.length === 0) {
            return
        }

        var trimmed = stdoutText.trim()
        if (trimmed.length === 0) {
            setAccountError(providerID, stderrText.trim().length > 0 ? boundedCliMessage(stderrText) : i18n("codexbar did not return account data."))
            return
        }

        // The ledger entry is already closed; still report a scoped error if
        // normalization fails after JSON parsing.
        try {
            var payload = JSON.parse(trimmed)

            var items = Array.isArray(payload) ? payload : [payload]
            var options = []
            var message = ""
            var sawMissingTokenAccountsError = false
            var itemLimit = Math.min(items.length, maximumAccountSnapshots)
            for (var i = 0; i < itemLimit; i++) {
                var item = items[i]
                if (!isCliRecord(item)) {
                    continue
                }
                var normalized = null
                try {
                    var accountItem = copyObject(item)
                    accountItem.provider = providerID
                    normalized = normalizeProvider(accountItem)
                } catch (recordError) {
                    // Contain the failure to its own record: a malformed
                    // sibling must not discard the accounts parsed before or
                    // after it.
                    message = i18n("Could not read a codexbar account record: %1",
                        boundedCliMessage(recordError.message))
                    continue
                }
                if (normalized.error.length > 0 && accountLabel(normalized).length === 0) {
                    if (Normalizer.isMissingTokenAccountsError(normalized.error)) {
                        sawMissingTokenAccountsError = true
                    } else {
                        message = normalized.error
                    }
                    continue
                }
                options.push(normalized)
            }

            var dedupedOptions = Normalizer.dedupeAccountOptions(options)
            var accountError = ""
            if (dedupedOptions.length === 0) {
                if (message.length > 0) {
                    accountError = message
                } else if (items.length > 0 && !sawMissingTokenAccountsError) {
                    accountError = i18n("codexbar did not return account data.")
                }
            }
            if (accountError.length === 0) {
                setAccountOptions(providerID, dedupedOptions)
            }
            setAccountError(providerID, accountError)
        } catch (error) {
            setAccountError(providerID, i18n("Could not parse codexbar account JSON: %1", error.message))
        }
    }

    function parseCostOutput(stdoutText, stderrText, requestedHistoryDays) {
        var trimmed = stdoutText.trim()
        if (trimmed.length === 0) {
            costErrorText = stderrText.trim().length > 0 ? boundedCliMessage(stderrText) : i18n("codexbar cost did not return JSON.")
            applyTokenCosts()
            return
        }

        var payload
        try {
            payload = JSON.parse(trimmed)
        } catch (error) {
            costErrorText = i18n("Could not parse codexbar cost JSON: %1", error.message)
            applyTokenCosts()
            return
        }

        var items = Normalizer.normalizeCostEnvelope(payload)
        if (items === null) {
            costErrorText = i18n("codexbar cost returned an unsupported JSON payload.")
            applyTokenCosts()
            return
        }
        var nextCosts = ({})
        var costMessage = ""
        var hadCostRecordError = false
        var failedCostProviderIDs = []
        for (var i = 0; i < items.length; i++) {
            var item = items[i]
            if (!isCliRecord(item)) {
                continue
            }
            var itemHasCostError = Normalizer.costRecordHasError(item)
            if (itemHasCostError) {
                hadCostRecordError = true
                failedCostProviderIDs.push(item.provider)
            }
            if (costMessage.length === 0 && item && item.error && item.error.message) {
                costMessage = boundedCliMessage(item.error.message)
            }
            if (itemHasCostError) {
                continue
            }
            var cost = normalizeTokenCost(item, requestedHistoryDays)
            var providerID = cost ? providerMapKey(cost.provider) : ""
            if (cost && providerID.length > 0) {
                nextCosts[providerID] = cost
            }
        }

        if (hadCostRecordError) {
            // A partial reply retains only failed providers from the same
            // command source. After a source change the retained map still
            // holds the previous context's snapshots, and merging them here
            // would re-tag old costs with the new source.
            var previousCosts = tokenCostsContext === costCommandSource
                ? tokenCosts
                : ({})
            tokenCosts = Normalizer.mergeCostSnapshotsAfterPartialFailure(
                previousCosts, nextCosts, failedCostProviderIDs)
            tokenCostsContext = costCommandSource
            costErrorText = costMessage
            if (costErrorText.length === 0) {
                costErrorText = i18n("Some cost data could not be refreshed.")
            }
        } else {
            tokenCosts = nextCosts
            tokenCostsContext = costCommandSource
            costErrorText = ""
        }
        applyTokenCosts()
    }

    function parseSessionsOutput(stdoutText, stderrText) {
        sessionsLoading = false
        var trimmed = stdoutText.trim()
        if (trimmed.length === 0) {
            sessionsErrorText = stderrText.trim().length > 0
                ? boundedCliMessage(stderrText)
                : i18n("codexbar sessions did not return JSON.")
            return
        }

        var payload
        try {
            payload = JSON.parse(trimmed)
        } catch (error) {
            sessionsErrorText = i18n("Could not parse codexbar sessions JSON: %1", error.message)
            return
        }

        // null means the payload shape is unsupported. It must not read as an
        // empty successful snapshot, which would wipe the visible sessions.
        var nextSessions = Normalizer.normalizeSessions(payload)
        if (nextSessions === null) {
            sessionsErrorText = i18n("codexbar sessions returned an unsupported JSON payload.")
            return
        }
        sessions = nextSessions
        sessionsErrorText = ""
        sessionsLastCompletedAtMs = Date.now()
        sessionsLoadedCommandSource = sessionsCommandSource
        sessionsLastUpdatedText = i18n("Updated %1", Qt.formatDateTime(new Date(), "hh:mm"))
    }

    function sessionTitle(item, index) {
        if (privacyMode) {
            return i18n("Session %1", (index >= 0 ? index : 0) + 1)
        }
        if (!item) {
            return i18n("Untitled session")
        }
        return item.projectName.length > 0
            ? item.projectName
            : (item.sessionName.length > 0 ? item.sessionName : i18n("Untitled session"))
    }

    function sessionSubtitle(item) {
        item = PrivacyPresentation.session(item, privacyMode)
        if (!item) {
            return ""
        }
        var details = []
        if (item.provider.length > 0) {
            details.push(providerDisplayTitle(item.provider))
        }
        if (item.host.length > 0) {
            details.push(item.host)
        }
        if (item.source.length > 0) {
            details.push(sessionSourceText(item.source))
        }
        return details.join(" - ")
    }

    function sessionStateText(state) {
        switch (state) {
        case "active":
            return i18n("Active")
        case "idle":
            return i18n("Idle")
        case "running":
            return i18n("Running")
        case "working":
            return i18n("Working")
        case "":
        case "unknown":
            return i18n("Unknown")
        default:
            return privacyMode ? i18n("Unknown") : capitalize(state)
        }
    }

    function sessionSourceText(source) {
        switch (source) {
        case "cli":
            return i18n("Command line")
        case "desktopApp":
            return i18n("Desktop app")
        case "ide":
            return i18n("IDE")
        case "unknown":
            return i18n("Unknown")
        default:
            return privacyMode ? i18n("Unknown") : source
        }
    }

    function sessionActivityText(item, nowMs) {
        if (!item || !isFinite(Number(item.activityMs)) || Number(item.activityMs) <= 0) {
            return ""
        }
        var currentTimeMs = Number(nowMs)
        if (!isFinite(currentTimeMs) || currentTimeMs <= 0) {
            currentTimeMs = Date.now()
        }
        var elapsedSeconds = Math.max(0, Math.floor((currentTimeMs - Number(item.activityMs)) / 1000))
        if (elapsedSeconds < 60) {
            return i18n("Just now")
        }
        var minutes = Math.floor(elapsedSeconds / 60)
        if (minutes < 60) {
            return i18np("%1 minute ago", "%1 minutes ago", minutes)
        }
        var hours = Math.floor(minutes / 60)
        if (hours < 24) {
            return i18np("%1 hour ago", "%1 hours ago", hours)
        }
        var days = Math.floor(hours / 24)
        return i18np("%1 day ago", "%1 days ago", days)
    }

    function normalizeTokenCost(item, requestedHistoryDays) {
        if (!item || !item.provider) {
            return null
        }

        var providerID = providerMapKey(item.provider)
        if (providerID.length === 0) {
            return null
        }
        var currency = Normalizer.boundedDisplayText(item.currencyCode || "USD", 12)
        var emittedHistoryDays = Normalizer.strictFiniteNumber(item.historyDays)
        var fallbackHistoryDays = Normalizer.strictFiniteNumber(requestedHistoryDays)
        var historyDays = isFinite(emittedHistoryDays) && emittedHistoryDays > 0
            ? Math.max(1, Math.min(maximumCostHistoryPoints, Math.floor(emittedHistoryDays)))
            : (isFinite(fallbackHistoryDays) && fallbackHistoryDays > 0
            ? Math.max(1, Math.min(maximumCostHistoryPoints, Math.floor(fallbackHistoryDays)))
            : 30)
        var windowLabel = Normalizer.boundedDisplayText(item.historyLabel || costHistoryWindowLabel(item, historyDays), 120)
        var trust = Normalizer.normalizeCostTrustMetadata(item)
        var totals = Normalizer.normalizeProviderCostTotals(
            providerID, item.totals, item.last30DaysCostUSD,
            item.last30DaysTokens, currency)
        var trustSummary = CostPresentation.costTrustSummary([{
            totals: totals,
            trust: trust
        }])
        var valueMode = trustSummary ? trustSummary.valueMode : "plain"
        var today = Normalizer.normalizeProviderCostTotals(
            providerID, null, item.sessionCostUSD, item.sessionTokens, currency)
        var modelSummary = Normalizer.normalizeCostModels(item.daily, currency, historyDays, item.updatedAt)
        return {
            provider: providerID,
            historyDays: historyDays,
            // Older payloads omit the flag; absent means "do not warn".
            historyCoverageEstablished: item.historyCoverageIsEstablished !== false,
            trust: trust,
            valueMode: valueMode,
            windowLabel: windowLabel,
            today: today,
            title: i18n("Cost"),
            // Top-level coverage/provenance describes the requested history
            // window, not the independently emitted current-session figure.
            sessionLine: costLine(i18n("Today"),
                Normalizer.normalizeProviderCostAmount(providerID, item.sessionCostUSD),
                item.sessionTokens, currency),
            monthLine: costLine(windowLabel, totals.cost, totals.tokens,
                currency, valueMode),
            windowValueLine: costValueLine(
                totals.cost, totals.tokens, currency, valueMode),
            hintLine: tokenCostHint(providerID),
            totals: totals,
            projects: Normalizer.normalizeCostProjects(item.projects, currency),
            models: modelSummary.rows,
            modelsTruncated: modelSummary.truncated,
            daily: Normalizer.normalizeCostDaily(item.daily, currency, historyDays, item.updatedAt)
        }
    }

    function costHistoryWindowLabel(item, requestedHistoryDays) {
        var rawDays = item && item.historyDays !== undefined && item.historyDays !== null
            ? Normalizer.strictFiniteNumber(item.historyDays)
            : NaN
        if (!isFinite(rawDays) || rawDays <= 0) {
            rawDays = Normalizer.strictFiniteNumber(requestedHistoryDays)
        }
        if (!isFinite(rawDays) || rawDays <= 0) {
            return i18n("Last 30 days")
        }
        var days = Math.max(1, Math.floor(rawDays))
        return days === 1 ? i18n("Today") : i18np("Last %1 day", "Last %1 days", days)
    }

    function paintRoundedTopBar(context, x, baseline, width, height, radius) {
        CostPresentation.paintRoundedTopBar(context, x, baseline, width, height, radius)
    }

    function chartBarGeometry(width, count) {
        return CostPresentation.chartBarGeometry(width, count)
    }

    function chartLineX(width, count, index, inset) {
        return CostPresentation.chartLineX(width, count, index, inset)
    }

    function chartLineIndexAt(width, count, positionX, inset) {
        return CostPresentation.chartLineIndexAt(width, count, positionX, inset)
    }

    function chartLineY(height, fraction, inset) {
        return CostPresentation.chartLineY(height, fraction, inset)
    }

    function buildChartBarGradient(context, accent, baseline, topOpacity, bottomOpacity) {
        var gradient = context.createLinearGradient(0, 0, 0, Math.max(1, baseline))
        gradient.addColorStop(0, canvasColor(accent, topOpacity))
        gradient.addColorStop(1, canvasColor(accent, bottomOpacity))
        return gradient
    }

    function costSparklineSummary(points) {
        var summary = CostPresentation.sparklineSummary(costNumberFormat, points, costHistoryShowsTokens)
        if (!summary) {
            return ""
        }
        return i18n("%1: %2", summary.label.length > 0 ? summary.label : i18n("Latest"), summary.value)
    }

    function costChartPoints(points) {
        return CostPresentation.chartPoints(costNumberFormat, points, costHistoryShowsTokens)
    }

    function spendProviderCosts() {
        // The Usage & Spend tab reads the map directly, so it needs the same
        // source check as the provider meters: never total the previous
        // context's costs beside the new context's quotas.
        if (tokenCostsContext !== costCommandSource) {
            return []
        }
        var snapshots = CostPresentation.spendSnapshots(tokenCosts, costHistoryDays, function(providerID) {
            return providerTitle(providerID)
        })
        return snapshots.map(function(item) { return root.costPresentation(item) })
    }

    function presentedSpendProviderCosts(costs) {
        return ProviderOrder.orderedItems(costs || spendProviderCosts(), providerOrderRaw)
    }

    function spendDailyPoints() {
        return CostPresentation.spendDailyPoints(costNumberFormat, spendProviderCosts(), costHistoryShowsTokens)
    }

    function spendCurrency(costs) {
        return CostPresentation.spendCurrency(costs || spendProviderCosts())
    }

    function spendTotalLine() {
        var costs = spendProviderCosts()
        var totals = CostPresentation.spendTotals(costs)
        if (!totals) {
            return ""
        }
        var numericCost = Normalizer.strictFiniteNumber(totals.cost)
        var hasTokens = CostPresentation.hasMetricValue(totals, true)
        if (!isFinite(numericCost)) {
            return hasTokens ? usageCountText(totals.tokens, "tokens")
                : i18n("Tokens unavailable")
        }
        var trustSummary = CostPresentation.costTrustSummary(costs)
        var costValue = qualifiedCostValue(
            CostPresentation.amountString(costNumberFormat, numericCost, totals.currency),
            trustSummary ? trustSummary.valueMode : "plain")
        if (!hasTokens) {
            return totals.hasMixedCostCurrencies ? i18n("%1 subtotal", costValue) : i18n("%1 total", costValue)
        }
        var totalText = totals.hasMixedCostCurrencies
            ? i18n("%1 subtotal", costValue) : i18n("%1 total", costValue)
        return i18n("%1 - %2", totalText, usageCountText(totals.tokens, "tokens"))
    }

    function updateCostTrustNoticeState(scope, summary, shouldDismiss) {
        var transition = CostPresentation.costTrustNoticeStoreTransition(
            summary, costTrustNoticeStates, scope, shouldDismiss)
        costTrustNoticeStates = transition.states
        return transition.state
    }

    function setCostHistoryDays(days) {
        var nextDays = Math.max(1, Math.min(maximumCostHistoryPoints, Math.floor(Number(days) || 30)))
        Plasmoid.configuration.costHistoryDays = nextDays
    }

    function setCostHistoryMetric(metric) {
        Plasmoid.configuration.costHistoryMetric = safeCostHistoryMetric(metric)
    }

    // The CLI reports whether its local log scan already covers the requested
    // window; until it does, the earliest bars are short for a scan reason
    // rather than a spend reason.
    function spendHistoryStillBuilding() {
        return CostPresentation.historyStillBuilding(spendProviderCosts())
    }

    function costBreakdownRows(tokenCost) {
        if (!tokenCost || !tokenCost.totals) {
            return []
        }

        var totals = tokenCost.totals
        return CostPresentation.breakdownRows([
            { label: i18n("Total tokens"), tokens: totals.tokens },
            { label: i18n("Input"), tokens: totals.inputTokens },
            { label: i18n("Output"), tokens: totals.outputTokens },
            { label: i18n("Cache read"), tokens: totals.cacheReadTokens },
            { label: i18n("Cache write"), tokens: totals.cacheCreationTokens }
        ])
    }

    function costModelRows(tokenCost) {
        return CostPresentation.modelRows(costNumberFormat, tokenCost, function(tokens) {
            return usageCountText(tokens, "tokens")
        })
    }

    function costHistoryRows(tokenCost) {
        return CostPresentation.historyRows(costNumberFormat, tokenCost, costHistoryShowsTokens, i18n("Latest"))
    }

    function costPeakLine(points) {
        var peak = CostPresentation.peakPoint(points, costHistoryShowsTokens)
        if (!peak) {
            return ""
        }
        return i18n("Peak: %1 - %2",
            peak.label.length > 0 ? peak.label : i18n("Latest"),
            costHistoryShowsTokens
                ? CostPresentation.tokenCountString(peak.magnitude)
                : CostPresentation.amountString(costNumberFormat, peak.magnitude, peak.currency))
    }

    function costAverageDailyLine(points) {
        var average = CostPresentation.averageDailyValue(points, costHistoryShowsTokens)
        if (!average) {
            return ""
        }
        return i18n("Average/day: %1", costHistoryShowsTokens
            ? CostPresentation.tokenCountString(average.value)
            : CostPresentation.amountString(costNumberFormat, average.value, average.currency))
    }

    function costPerMillionLine(tokenCost) {
        var perMillion = CostPresentation.perMillionAmount(tokenCost)
        if (!perMillion) {
            return ""
        }
        return i18n("Average: %1 / 1M tokens",
            CostPresentation.amountString(costNumberFormat, perMillion.value, perMillion.currency))
    }

    function usageDashboard(usage, item) {
        var dashboard = LegacyUsageDashboard.normalize(usage, item)
        if (!dashboard) {
            return null
        }
        return {
            kpis: dashboard.kpis.map(dashboardDisplayRow),
            rows: dashboard.rows.map(dashboardDisplayRow)
        }
    }

    function dashboardLabelText(labelKey) {
        var labels = {
            codexDashboard: i18n("Codex dashboard"),
            openaiApi: i18n("OpenAI API"),
            openRouter: i18n("OpenRouter"),
            claudeAdmin: i18n("Claude Admin"),
            poe: i18n("Poe"),
            deepseek: i18n("DeepSeek"),
            minimax: i18n("MiniMax"),
            zai: i18n("Z.ai"),
            codeReviewRemaining: i18n("Code review remaining"),
            creditsRemaining: i18n("Credits remaining"),
            plan: i18n("Plan"),
            signedIn: i18n("Signed in"),
            today: i18n("Today"),
            last7Days: i18n("7d"),
            last30Days: i18n("30d"),
            currentMonth: i18n("This month"),
            topModel: i18n("Top model"),
            usageMix: i18n("Usage mix"),
            latest: i18n("Latest"),
            latestDashboardDay: i18n("Latest dashboard day")
        }
        return labels[labelKey] || ""
    }

    function dashboardPartText(part) {
        switch (part.kind) {
        case "text":
            return part.value
        case "percent":
            return i18n("%1%", Math.round(part.value))
        case "currency":
            return amountString(part.value, part.currency)
        case "tokens":
        case "requests":
        case "points":
            return usageCountText(part.value, part.kind)
        default:
            return tokenCountString(part.value)
        }
    }

    function dashboardDisplayRow(row) {
        var parts = row.parts.map(dashboardPartText)
        if (row.name.length > 0) {
            parts = [parts.length > 0 ? i18n("%1 (%2)", row.name, parts[0]) : row.name]
        }
        return {
            label: row.labelKey.length > 0 ? dashboardLabelText(row.labelKey) : row.label,
            value: Normalizer.boundedDisplayText(parts.join(" · "), 500)
        }
    }

    function providerTokenCost(providerID) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return null
        }
        // A snapshot that outruns a command-source change must never attach to
        // the new context's providers, even if the history range still matches.
        if (tokenCostsContext !== costCommandSource) {
            return null
        }
        var snapshot = tokenCosts[key] || null
        return CostPresentation.snapshotMatchesRange(snapshot, costHistoryDays)
            ? snapshot
            : null
    }

    function applyTokenCosts() {
        if (!providers || providers.length === 0) {
            return
        }

        var nextProviders = []
        for (var i = 0; i < providers.length; i++) {
            var item = copyObject(providers[i])
            item.tokenCost = item.usageStale === true ? null : providerTokenCost(item.provider)
            nextProviders.push(item)
        }
        providers = nextProviders
    }

    function selectedAccountForProvider(providerID) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return ""
        }
        var selected = selectedAccounts[key]
        return selected ? String(selected) : ""
    }

    function accountOptionsForProvider(providerID) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return []
        }
        return accountOptions[key] || []
    }

    function accountErrorForProvider(providerID) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return ""
        }
        return privateErrorText(accountErrors[key] ? String(accountErrors[key]) : "")
    }

    function accountLoadingForProvider(providerID) {
        var key = providerMapKey(providerID)
        return key.length > 0 && AccountRequests.isLoading(activeCommandDescriptors, key)
    }

    function setAccountOptions(providerID, options) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return
        }
        var next = copyObject(accountOptions)
        next[key] = options || []
        accountOptions = next
    }

    function setAccountError(providerID, message) {
        var next = copyObject(accountErrors)
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return
        }
        var cleanMessage = boundedCliMessage(message)
        if (cleanMessage.length > 0) {
            next[key] = cleanMessage
        } else {
            delete next[key]
        }
        accountErrors = next
    }

    function privateErrorText(text) {
        return PrivacyPresentation.errorText(text, privacyMode,
            i18n("Details hidden by privacy mode."))
    }

    function accountDisplayLabel(item, index) {
        return privacyMode ? i18n("Account %1", index + 1) : accountLabel(item)
    }

    function costPresentation(item) {
        var result = PrivacyPresentation.cost(item, privacyMode)
        if (!privacyMode || !result) {
            return result
        }
        result.title = i18n("Cost")
        result.sessionLine = costLine(i18n("Today"), result.today.cost,
            result.today.tokens, result.today.currency)
        var trust = CostPresentation.costTrustSummary([result])
        result.valueMode = trust ? trust.valueMode : "plain"
        result.windowLabel = costHistoryWindowLabel(result, result.historyDays)
        result.monthLine = costLine(result.windowLabel,
            result.totals.cost, result.totals.tokens, result.totals.currency,
            result.valueMode)
        result.windowValueLine = costValueLine(result.totals.cost, result.totals.tokens,
            result.totals.currency, result.valueMode)
        result.hintLine = tokenCostHint(result.provider)
        for (var i = 0; i < result.models.length; i++) {
            result.models[i].label = i18n("Model %1", i + 1)
        }
        for (var dayIndex = 0; dayIndex < result.daily.length; dayIndex++) {
            var dayModels = result.daily[dayIndex].models
            for (var modelIndex = 0; modelIndex < dayModels.length; modelIndex++) {
                dayModels[modelIndex].label = i18n("Model %1", modelIndex + 1)
            }
        }
        for (var j = 0; j < result.projects.rows.length; j++) {
            result.projects.rows[j].label = i18n("Project %1", j + 1)
        }
        return result
    }

    function providerPresentations(items) {
        return items.map(function(item) { return root.providerPresentation(item) })
    }

    function providerPresentation(item) {
        if (!privacyMode || !item) {
            return item
        }
        var result = PrivacyPresentation.provider(item, true, {
            title: providerDisplayTitle(item.provider),
            account: i18n("Account hidden"),
            status: statusBadgeText(item.statusSeverity),
            error: i18n("Details hidden by privacy mode."),
            placeholder: i18n("No usage yet"),
            usage: i18n("Usage"),
            rowLabels: (item.rows || []).map(function(row) {
                return root.rateWindowLabel(item.provider, row.lane)
            })
        })
        result.tokenCost = costPresentation(item.tokenCost)
        return result
    }

    function accountLabel(item) {
        return Normalizer.accountLabel(item)
    }

    // The stable `--account` identity. Display labels may collapse spacing
    // that still distinguishes two accounts, so selection, matching, and the
    // CLI argument compare keys, never labels.
    function accountKey(item) {
        return Normalizer.accountKey(item)
    }

    function accountSubtitle(item) {
        if (privacyMode || !item) {
            return ""
        }
        var parts = []
        if (item.loginMethod && item.loginMethod.length > 0) {
            parts.push(item.loginMethod)
        }
        if (item.organization && item.organization.length > 0 && item.organization !== item.account) {
            parts.push(item.organization)
        }
        return parts.join(" · ")
    }

    function accountIsSelected(option, currentItem) {
        if (!option) {
            return false
        }
        var identity = accountKey(option)
        var selected = selectedAccountForProvider(option.provider)
        if (selected.length > 0) {
            return identity.length > 0 && identity === selected
        }
        // A presented snapshot may replace the account label with a placeholder.
        // Selection always compares against the original account identity.
        var currentIndex = currentItem ? providerIndexForID(currentItem.provider) : -1
        var accountItem = currentIndex >= 0 ? providers[currentIndex] : currentItem
        return accountItem && accountItem.provider === option.provider && identity.length > 0 && identity === accountKey(accountItem)
    }

    function selectAccount(providerID, accountIdentity) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return
        }
        var identity = String(accountIdentity || "")
        var next = copyObject(selectedAccounts)
        if (identity.length > 0) {
            next[key] = identity
        } else {
            delete next[key]
        }
        invalidateUsageData(key)
        selectedAccounts = next
        setNotificationProviderRefreshPending(key, true)

        var options = accountOptionsForProvider(key)
        for (var i = 0; i < options.length; i++) {
            if (root.accountKey(options[i]) === identity) {
                replaceProviderSnapshot(key, options[i])
                scheduleUsageRefresh()
                return
            }
        }
        // scheduleUsageRefresh coalesces with the onCommandSourceChanged trigger
        // that the selectedAccounts write above fires in single-provider mode;
        // a direct callLater(refreshNow) would start one CLI run and immediately
        // retire it in favour of a second.
        scheduleUsageRefresh()
    }

    function replaceProviderSnapshot(providerID, snapshot) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return
        }
        var replacement = UsageCache.reconcile([], [snapshot], Date.now())[0]
        replacement.tokenCost = replacement.usageStale === true ? null : providerTokenCost(key)
        var nextProviders = []
        for (var i = 0; i < providers.length; i++) {
            nextProviders.push(providers[i].provider === key ? replacement : providers[i])
        }
        if (!nextProviders.some(function(item) { return item.provider === key })) {
            nextProviders.push(replacement)
        }
        providers = ProviderOrder.orderedItems(nextProviders, providerOrderRaw)
    }

    function normalizeProvider(item) {
        var usage = isCliRecord(item.usage) ? item.usage : ({})
        var pace = isCliRecord(item.pace) ? item.pace : ({})
        var rows = []
        var providerID = providerMapKey(item.provider || "unknown")
        if (providerID.length === 0) {
            providerID = "unknown"
        }

        var primaryRow = addWindow(rows, rateWindowLabel(providerID, "primary"), usage.primary, pace.primary, true, "primary")
        addWindow(rows, rateWindowLabel(providerID, "secondary"), usage.secondary, pace.secondary, true, "secondary")
        addWindow(rows, rateWindowLabel(providerID, "tertiary"), usage.tertiary, pace.tertiary, true, "tertiary")

        var extras = Array.isArray(usage.extraRateWindows) ? usage.extraRateWindows : []
        var extraLimit = Math.min(extras.length, maximumExtraRateWindows)
        for (var i = 0; i < extraLimit; i++) {
            var extra = extras[i]
            if (isCliRecord(extra) && isCliRecord(extra.window)) {
                addWindow(rows, Normalizer.boundedDisplayText(extra.title || extra.id || i18n("Extra"), 120), extra.window, null, extra.usageKnown !== false, "extra")
            }
        }

        var identity = isCliRecord(usage.identity) ? usage.identity : ({})
        var error = isCliRecord(item.error) ? item.error : null
        var status = isCliRecord(item.status) ? item.status : null
        var severity = Normalizer.statusSeverity(status)
        var credits = isCliRecord(item.credits) ? item.credits : null
        var codexCreditLimit = Normalizer.normalizeCodexCreditLimit(
            providerID,
            credits && hasOwnKey(credits, "codexCreditLimit")
                ? credits.codexCreditLimit
                : null)
        var displayName = item.displayName || item.title || providerDisplayNames[providerID] || ""
        // Identity fields are CLI-controlled and may carry structured values.
        // A truthy object must not mask a valid fallback, so each candidate is
        // validated before it wins; a non-string candidate never does.
        var rawAccount = Normalizer.firstValidAccountIdentity(
            [item.account, identity.accountEmail, usage.accountEmail])
        var rawOrganization = Normalizer.firstValidAccountIdentity(
            [identity.accountOrganization, usage.accountOrganization])
        var rawLoginMethod = Normalizer.firstValidAccountIdentity(
            [identity.loginMethod, usage.loginMethod])
        var providerDetails = UsageDetails.normalizeSections(usage.details)
        var providerUsageDashboard = providerDetails.length > 0 ? null : usageDashboard(usage, item)
        var hasSupplementalUsage = providerDetails.length > 0
            || providerUsageDashboard !== null
            || codexCreditLimit !== null
        var placeholder = providerPlaceholder(providerID, rows, usage, item, error, hasSupplementalUsage)
        var creditsRemaining = credits
            ? Normalizer.strictFiniteNumber(credits.remaining)
            : Number.NaN

        return {
            provider: providerID,
            title: Normalizer.boundedDisplayText(providerTitle(providerID, displayName), 120),
            source: Normalizer.boundedDisplayText(item.source || "", 120),
            version: Normalizer.boundedDisplayText(item.version || "", 120),
            account: Normalizer.boundedDisplayText(rawAccount, 256),
            organization: Normalizer.boundedDisplayText(rawOrganization, 256),
            loginMethod: Normalizer.boundedDisplayText(rawLoginMethod, 120),
            // The `--account` identity keeps the original spacing: display text
            // may collapse it, but selection and the CLI argument must not.
            accountKey: Normalizer.accountKey({ account: rawAccount, organization: rawOrganization, loginMethod: rawLoginMethod }),
            rows: rows,
            primaryRow: primaryRow,
            providerDetails: providerDetails,
            usageDashboard: providerUsageDashboard,
            providerCost: providerCostSection(providerID, usage.providerCost),
            resetCredits: resetCreditsSection(providerID, usage.codexResetCredits),
            tokenCost: providerTokenCost(providerID),
            codexCreditLimit: codexCreditLimit,
            planText: Normalizer.boundedDisplayText(planText(providerID, usage, item), 120),
            dashboardUrl: providerDashboardUrl(providerID),
            statusUrl: safeStatusUrl(providerID, status && status.url ? status.url : ""),
            changelogUrl: providerChangelogUrl(providerID),
            credits: isFinite(creditsRemaining)
                ? creditsRemaining
                : null,
            status: Normalizer.boundedDisplayText(status ? statusText(status) : "", 500),
            statusKnown: status !== null,
            statusSeverity: severity,
            statusIncidentKey: Normalizer.boundedDisplayText(Normalizer.statusIncidentKey(status), 128),
            hasIncident: severity.length > 0,
            error: boundedCliMessage(error && error.message ? error.message : ""),
            placeholder: placeholder,
            updatedAt: Normalizer.boundedDisplayText(usage.updatedAt || (credits ? credits.updatedAt : ""), 128)
        }
    }

    function providerPlaceholder(providerID, rows, usage, item, error, hasSupplementalUsage) {
        if ((rows && rows.length > 0) || hasSupplementalUsage === true) {
            return ""
        }

        var message = error && error.message ? String(error.message).trim() : ""
        if (message.length > 0 && message !== "Found sessions, but no rate limit events yet.") {
            return ""
        }

        if (rateLimitsUnavailable(providerID, usage, item)) {
            return i18n("Limits not available")
        }

        return i18n("No usage yet")
    }

    function rateLimitsUnavailable(providerID, usage, item) {
        var key = providerKey(providerID)
        if (key !== "antigravity" && key !== "doubao" && key !== "codex") {
            return false
        }

        var identity = usage && usage.identity ? usage.identity : ({})
        var hasIdentity = (item && item.account && item.account.length > 0)
            || (identity.accountEmail && identity.accountEmail.length > 0)
            || (identity.accountOrganization && identity.accountOrganization.length > 0)
            || (identity.loginMethod && identity.loginMethod.length > 0)
        if (!hasIdentity) {
            return false
        }

        return !usage.primary && !usage.secondary && !usage.tertiary
    }

    // The clamped percentages come from the normalizer; the words stay here,
    // because every text field below is either translated or formatted against
    // the current theme/locale, which a `.pragma library` may not reach.
    function addWindow(rows, label, window, pace, usageKnown, lane) {
        var metrics = Normalizer.rateWindowMetrics(window, pace, usageKnown)
        if (metrics === null) {
            return null
        }

        var row = {
            lane: lane || "",
            label: Normalizer.boundedDisplayText(label, 120),
            hasPercent: metrics.hasPercent,
            usedPercent: metrics.usedPercent,
            leftPercent: metrics.leftPercent,
            paceKnown: metrics.paceKnown,
            pacePercent: metrics.pacePercent,
            paceOnTop: metrics.paceOnTop,
            paceEtaSeconds: metrics.paceEtaSeconds,
            resetsAt: Normalizer.boundedDisplayText(
                window.resetsAt === undefined || window.resetsAt === null ? "" : window.resetsAt,
                128),
            resetDescription: Normalizer.boundedDisplayText(window.resetDescription || "", 500),
            reset: Normalizer.boundedDisplayText(resetText(window, false), 500),
            pace: paceSummaryText(pace)
        }
        rows.push(row)
        return row
    }

    function rateWindowLabel(providerID, lane) {
        var key = providerKey(providerID)
        if (lane === "primary") {
            switch (key) {
            case "alibaba":
            case "opencode":
            case "opencodego":
                return i18n("5-hour")
            case "amp":
                return i18n("Amp Free")
            case "antigravity":
                return i18n("Gemini Models")
            case "azureopenai":
                return i18n("Status")
            case "bedrock":
                return i18n("Budget")
            case "commandcode":
            case "manus":
                return i18n("Monthly credits")
            case "copilot":
                return i18n("Premium")
            case "cursor":
                return i18n("Total")
            case "factory":
                return i18n("Standard")
            case "doubao":
            case "grok":
            case "groq":
            case "vertexai":
                return i18n("Requests")
            case "gemini":
                return i18n("Pro")
            case "kilo":
            case "kiro":
            case "mimo":
            case "warp":
            case "abacus":
                return i18n("Credits")
            case "kimi":
                return i18n("Weekly")
            case "minimax":
                return i18n("Prompts")
            case "openai":
                return i18n("Spend")
            case "openrouter":
                return i18n("API key limit")
            case "poe":
                return i18n("Points")
            case "zed":
                return i18n("Edit predictions")
            default:
                return i18n("Session")
            }
        }
        if (lane === "secondary") {
            switch (key) {
            case "antigravity":
                return i18n("Claude and GPT")
            case "amp":
                return i18n("Balance")
            case "azureopenai":
                return i18n("Deployment")
            case "bedrock":
                return i18n("Cost")
            case "copilot":
                return i18n("Chat")
            case "cursor":
                return i18n("Auto")
            case "factory":
                return i18n("Premium")
            case "doubao":
            case "kimi":
                return i18n("Rate limit")
            case "gemini":
                return i18n("Flash")
            case "grok":
                return i18n("On-demand")
            case "groq":
            case "vertexai":
                return i18n("Tokens")
            case "kilo":
                return i18n("Kilo Pass")
            case "kiro":
                return i18n("Bonus")
            case "mimo":
            case "minimax":
                return i18n("Window")
            case "openai":
                return i18n("Requests")
            case "warp":
                return i18n("Add-on credits")
            case "zed":
                return i18n("Billing cycle")
            default:
                return i18n("Weekly")
            }
        }
        if (lane === "tertiary") {
            if (key === "alibaba" || key === "opencodego") {
                return i18n("Monthly")
            }
            if (key === "claude") {
                return i18n("Sonnet")
            }
            if (key === "cursor") {
                return i18n("API")
            }
            if (key === "gemini") {
                return i18n("Flash Lite")
            }
            return i18n("Opus")
        }
        return i18n("Usage")
    }

    function providerCostSection(providerID, cost) {
        var key = providerKey(providerID)
        if (key === "manus" || key === "synthetic") {
            return null
        }

        if (!isCliRecord(cost)) {
            return null
        }

        var used = Normalizer.strictFiniteNumber(cost.used)
        var limit = Normalizer.strictFiniteNumber(cost.limit)
        var personalUsed = Normalizer.strictFiniteNumber(cost.personalUsed)
        var currency = Normalizer.boundedDisplayText(cost.currencyCode || "USD", 12)
        var period = Normalizer.boundedDisplayText(cost.period || i18n("This month"), 120)
        var hasUsed = isFinite(used)
        var hasLimit = isFinite(limit) && limit > 0
        if (!hasUsed) {
            return null
        }

        if (key === "factory" && period === "Extra usage balance") {
            return {
                title: i18n("Extra usage"),
                percentUsed: -1,
                spendLine: i18n("Balance: %1", amountString(used, currency)),
                percentLine: "",
                personalSpendLine: ""
            }
        }

        if (key === "opencodego" && period === "Zen balance") {
            return {
                title: i18n("Zen balance"),
                percentUsed: -1,
                spendLine: i18n("Balance: %1", amountString(used, currency)),
                percentLine: "",
                personalSpendLine: ""
            }
        }

        if (key === "minimax" && period === "MiniMax points balance") {
            return {
                title: i18n("Credits"),
                percentUsed: -1,
                spendLine: i18n("Balance: %1", Math.round(used)),
                percentLine: "",
                personalSpendLine: ""
            }
        }

        if (hasLimit) {
            var percent = clamp((used / limit) * 100, 0, 100)
            return {
                title: currency === "Quota" ? i18n("Quota usage") : i18n("Extra usage"),
                percentUsed: percent,
                spendLine: i18n("%1: %2 / %3", localizedPeriod(period), amountString(used, currency), amountString(limit, currency)),
                percentLine: i18n("%1% used", Math.round(percent)),
                personalSpendLine: isFinite(personalUsed) && personalUsed > 0
                    ? i18n("Your spend: %1", amountString(personalUsed, currency))
                    : ""
            }
        }

        if (key === "litellm") {
            return null
        }

        return {
            title: key === "openai" || key === "claude"
                ? i18n("API spend")
                : i18n("Extra usage"),
            percentUsed: -1,
            spendLine: i18n("%1: %2", localizedPeriod(period), amountString(used, currency)),
            percentLine: "",
            personalSpendLine: ""
        }
    }

    function resetCreditsSection(providerID, resetCredits) {
        if (providerKey(providerID) !== "codex" || !resetCredits) {
            return null
        }

        var count = Normalizer.strictFiniteNumber(resetCredits.availableCount)
        if (!isFinite(count) || count <= 0) {
            return null
        }

        return {
            title: i18n("Reset credits"),
            line: i18np("%1 available", "%1 available", Math.round(count))
        }
    }

    function codexCreditLimitUsageRow(creditLimit) {
        if (!creditLimit) {
            return null
        }
        return {
            lane: "monthlyCredits",
            label: creditLimit.title.length > 0
                ? creditLimit.title
                : i18n("Monthly credit limit"),
            hasPercent: true,
            usedPercent: creditLimit.usedPercent,
            leftPercent: creditLimit.leftPercent,
            pacePercent: -1,
            paceOnTop: true,
            paceEtaSeconds: 0,
            resetsAt: creditLimit.resetsAt,
            resetDescription: "",
            reset: "",
            pace: i18n("Used: %1, remaining: %2 of %3",
                formatNumber(creditLimit.used),
                formatNumber(creditLimit.remaining),
                formatNumber(creditLimit.limit))
        }
    }

    function resetText(window, absolute) {
        // Optional reset metadata is CLI-controlled and may carry structured
        // values: passing them to new Date() or String() throws inside
        // ToPrimitive and would discard the whole provider snapshot. Only
        // strings and numbers reach the date parser; anything else degrades
        // to no reset, keeping the valid quota visible.
        var resetsAt = window.resetsAt
        if (typeof resetsAt !== "string" && typeof resetsAt !== "number") {
            resetsAt = ""
        }
        if (!resetsAt) {
            return window.resetDescription && window.resetDescription.length > 0 ? window.resetDescription : ""
        }

        var date = new Date(resetsAt)
        if (isNaN(date.getTime())) {
            return String(resetsAt)
        }

        if (absolute === true) {
            return Qt.formatDateTime(date, "ddd HH:mm")
        }

        var remainingMs = date.getTime() - panelClockMs
        if (remainingMs <= 0) {
            return i18n("now")
        }
        var minutes = Math.max(1, Math.round(remainingMs / 60000))
        if (minutes < 60) {
            return i18np("%1 min", "%1 min", minutes)
        }
        var hours = Math.floor(minutes / 60)
        var restMinutes = minutes % 60
        if (hours < 24) {
            return restMinutes > 0 ? i18n("%1h %2m", hours, restMinutes) : i18np("%1h", "%1h", hours)
        }
        var days = Math.floor(hours / 24)
        var restHours = hours % 24
        return restHours > 0 ? i18n("%1d %2h", days, restHours) : i18np("%1d", "%1d", days)
    }

    function usageResetText(row) {
        row = PrivacyPresentation.quota(row, privacyMode, "")
        if (!row) {
            return ""
        }
        if (row.resetsAt || row.resetDescription) {
            return resetText({
                resetsAt: row.resetsAt || "",
                resetDescription: row.resetDescription || ""
            }, resetTimesShowAbsolute)
        }
        return String(row.reset || "")
    }

    function statusText(status) {
        // Status fields are CLI-controlled: read them without coercing objects,
        // whose missing toString would throw inside String().
        var indicator = Normalizer.safeScalarText(status.indicator)
        var description = Normalizer.safeScalarText(status.description).trim()
        if (indicator.length === 0 || indicator === "none") {
            return description
        }

        var labels = {
            "minor": i18n("Partial outage"),
            "major": i18n("Major outage"),
            "critical": i18n("Critical issue"),
            "maintenance": i18n("Maintenance"),
            "unknown": i18n("Status unknown")
        }
        var text = labels[indicator] || indicator
        return description.length > 0 ? text + ": " + description : text
    }

    function statusBadgeColor(severity) {
        switch (String(severity || "")) {
        case "critical":
        case "major":
            return Kirigami.Theme.negativeTextColor
        case "minor":
        case "maintenance":
            return Kirigami.Theme.neutralTextColor
        case "unknown":
            return Kirigami.Theme.textColor
        default:
            return "transparent"
        }
    }

    function statusBadgeText(severity) {
        switch (String(severity || "")) {
        case "critical":
            return i18n("Critical")
        case "major":
            return i18n("Major")
        case "minor":
            return i18n("Issue")
        case "maintenance":
            return i18n("Maint.")
        case "unknown":
            return i18n("Unknown")
        default:
            return ""
        }
    }

    function statusMessageType(severity) {
        switch (String(severity || "")) {
        case "critical":
        case "major":
            return Kirigami.MessageType.Error
        case "minor":
        case "maintenance":
            return Kirigami.MessageType.Warning
        default:
            return Kirigami.MessageType.Information
        }
    }

    function primaryIncidentProvider() {
        var ranked = {
            "critical": 5,
            "major": 4,
            "minor": 3,
            "maintenance": 2,
            "unknown": 1
        }
        var best = null
        var bestRank = 0
        for (var i = 0; i < providers.length; i++) {
            var item = providers[i]
            if (!item || item.statusKnown === false || item.hasIncident !== true) {
                continue
            }
            var rank = item.statusSeverity ? ranked[item.statusSeverity] || 0 : 0
            if (rank > bestRank) {
                best = item
                bestRank = rank
            }
        }
        return best
    }

    function quotaWarningMarkers(row) {
        if (!showQuotaWarningMarkers || !row || !row.hasPercent) {
            return []
        }
        return QuotaThresholds.markers(
            quotaWarningPercent,
            quotaCriticalPercent,
            usageBarsShowUsed)
    }

    // The thresholds used to surface only as two ticks on the provider detail
    // meter, so an almost exhausted window looked exactly like an idle one on
    // the panel and in the overview. Every meter fill reads its colour from
    // this one level; the provider accent stays in charge below the warning
    // step, and the same setting that hides the markers hides the colour.
    function quotaSeverity(row) {
        if (!showQuotaWarningMarkers || !row || !row.hasPercent) {
            return ""
        }
        return QuotaThresholds.level(row.usedPercent, quotaWarningPercent, quotaCriticalPercent)
    }

    function quotaMeterColor(row, accent) {
        var severity = quotaSeverity(row)
        return severity.length > 0 ? statusBadgeColor(severity) : accent
    }

    // Quota, pace, and reset memo state is threshold-derived and has to be
    // rebuilt whenever a setting changes. Provider status is not: a settings
    // change is not a status transition, so the status baseline survives the
    // reset. Dropping it would either re-announce an ongoing incident or, once
    // the first observation is silently primed, swallow an incident that starts
    // while the provider is still refreshing.
    function resetNotificationMemo() {
        notificationMemo = NotificationPlanner.transition(
            [], notificationMemo, ({ mode: "reset" })).nextMemo
        notificationsPrimed = false
        // Prime synchronously against the current snapshot so an incident that
        // starts between the reset and the next deferred pass cannot be
        // absorbed silently into the new baseline. The deferred call stays as
        // a coalesced follow-up for refreshes already in flight.
        processNotifications()
        Qt.callLater(processNotifications)
    }

    function notificationProviderRefreshPending(providerID) {
        var key = providerMapKey(providerID)
        return key.length > 0 && notificationRefreshPending[key] === true
    }

    function setNotificationProviderRefreshPending(providerID, pending) {
        var key = providerMapKey(providerID)
        if (key.length === 0) {
            return
        }
        var nextPending = copyObject(notificationRefreshPending)
        if (pending) {
            nextPending[key] = true
        } else {
            delete nextPending[key]
        }
        notificationRefreshPending = nextPending
    }

    function markNotificationProvidersFresh(items) {
        var nextPending = copyObject(notificationRefreshPending)
        for (var i = 0; i < items.length; i++) {
            var item = items[i]
            if (!item || (item.usageStale === true && item.statusKnown !== true)) {
                continue
            }
            var providerID = providerMapKey(item.provider)
            if (providerID.length === 0) {
                continue
            }
            var selectedAccount = selectedAccountForProvider(providerID)
            if (selectedAccount.length > 0 && accountKey(item) !== selectedAccount) {
                continue
            }
            // A failed account refresh can still carry fresh provider status.
            // The planner separately ignores missing quota evidence.
            delete nextPending[providerID]
        }
        notificationRefreshPending = nextPending
    }

    function notificationScopeKey(item) {
        if (!item) {
            return JSON.stringify(["", ""])
        }
        var providerID = providerMapKey(item.provider)
        var selectedAccount = selectedAccountForProvider(providerID)
        var currentAccount = selectedAccount.length > 0 ? selectedAccount : accountKey(item)
        return JSON.stringify([providerID, currentAccount])
    }

    function paceWarningActive(row) {
        return row && row.paceOnTop === false && Number(row.paceEtaSeconds) > 0
    }

    function paceSummaryText(pace) {
        var parts = PacePresentation.summaryParts(pace)
        var labels = []
        for (var i = 0; i < parts.length; i++) {
            var part = parts[i]
            switch (part.kind) {
            case "onTrack":
                labels.push(i18n("On pace"))
                break
            case "deficit":
                labels.push(i18n("%1% in deficit", part.percent))
                break
            case "reserve":
                labels.push(i18n("%1% in reserve", part.percent))
                break
            case "expected":
                labels.push(i18n("Expected %1% used", part.percent))
                break
            case "lasts":
                labels.push(i18n("Lasts until reset"))
                break
            case "runsOut":
                labels.push(part.seconds === 0 ? i18n("Runs out now")
                    : i18n("Runs out in %1", paceEtaText(part.seconds)))
                break
            case "fallback":
                labels.push(part.text)
                break
            }
        }
        return labels.join(" | ")
    }

    function paceEtaText(seconds) {
        var numericSeconds = Number(seconds)
        if (!isFinite(numericSeconds) || numericSeconds <= 0) {
            return i18n("now")
        }
        var minutes = Math.max(1, Math.round(numericSeconds / 60))
        if (minutes < 60) {
            return i18np("%1 minute", "%1 minutes", minutes)
        }
        var hours = Math.max(1, Math.round(minutes / 60))
        if (hours < 48) {
            return i18np("%1 hour", "%1 hours", hours)
        }
        var days = Math.max(1, Math.round(hours / 24))
        return i18np("%1 day", "%1 days", days)
    }

    // Usage at or above this percent arms a row for reset detection; once armed,
    // dropping to or below the floor fires a single "limit reset" notification.
    // Mirrors the macOS weekly-limit reset detector, scoped to limits the user
    // was actually near so routine short-window resets stay quiet.
    readonly property int limitResetArmThreshold: 80
    readonly property int limitResetFloor: 5

    function quotaNotificationLevel(row) {
        if (!row || !row.hasPercent) {
            return ""
        }
        return QuotaThresholds.level(
            row.usedPercent,
            quotaWarningPercent,
            quotaCriticalPercent)
    }

    // QML resolves account identity, refresh freshness, configured thresholds,
    // and the rows to display. The pure planner receives only semantic
    // observations and returns ordered intents; it never sees i18n or effects.
    function notificationObservationRows(item) {
        if (item && item.usageStale === true) {
            return []
        }
        var sourceRows = item && Array.isArray(item.rows) ? item.rows : []
        var result = []
        for (var i = 0; i < sourceRows.length; i++) {
            var row = sourceRows[i]
            result.push({
                lane: row && row.lane ? String(row.lane) : "",
                label: row && row.label ? String(row.label) : "",
                resetsAt: row && row.resetsAt ? String(row.resetsAt) : "",
                hasPercent: row && row.hasPercent === true,
                usedPercent: row ? Number(row.usedPercent) : NaN,
                quotaLevel: quotaNotificationLevel(row),
                paceKnown: row && row.paceKnown === true,
                paceActive: paceWarningActive(row)
            })
        }
        return result
    }

    function notificationObservations() {
        var result = []
        for (var i = 0; i < providers.length; i++) {
            var item = providers[i]
            if (!item) {
                continue
            }
            var rows = notificationObservationRows(item)
            result.push({
                providerIndex: i,
                providerID: providerMapKey(item.provider),
                scopeID: notificationScopeKey(item),
                pending: NotificationPlanner.observationPending(
                    (item.usageStale === true && item.statusKnown !== true)
                        || notificationProviderRefreshPending(item.provider),
                    String(item.error || "").length > 0,
                    item.statusKnown === true,
                    rows.length),
                errorPresent: String(item.error || "").length > 0,
                statusKnown: item.statusKnown === true,
                statusActive: item.hasIncident === true
                    && String(item.statusSeverity || "").length > 0
                    && String(item.status || "").length > 0,
                statusSeverity: String(item.statusSeverity || ""),
                statusIncidentKey: String(item.statusIncidentKey || ""),
                rows: rows
            })
        }
        return result
    }

    function notificationPlannerOptions(mode) {
        return {
            mode: mode,
            statusEnabled: includeStatus && notifyStatusIncidents,
            quotaEnabled: notifyQuotaWarnings,
            paceEnabled: notifyPredictivePaceWarnings,
            resetEnabled: notifyLimitResets,
            resetArmThreshold: limitResetArmThreshold,
            resetFloor: limitResetFloor
        }
    }

    function dispatchNotificationIntents(intents, observations) {
        for (var i = 0; i < intents.length; i++) {
            var intent = intents[i]
            var observation = observations[intent.observationIndex]
            var item = observation ? providers[observation.providerIndex] : null
            if (!item) {
                continue
            }
            if (intent.kind === "status") {
                sendPlasmaNotification(
                    i18n("%1 status issue", item.title),
                    item.status,
                    notificationUrgency(intent.severity))
                continue
            }

            var rows = Array.isArray(item.rows) ? item.rows : []
            var row = rows[intent.rowIndex]
            if (!row) {
                continue
            }
            if (intent.kind === "quota") {
                var body = i18n("%1 is %2% used", row.label, Math.round(row.usedPercent))
                var resetLine = resetLabel(usageResetText(row))
                if (resetLine.length > 0) {
                    body += ". " + resetLine
                }
                sendPlasmaNotification(
                    intent.severity === "major"
                        ? i18n("%1 quota critical", item.title)
                        : i18n("%1 quota warning", item.title),
                    body,
                    notificationUrgency(intent.severity))
            } else if (intent.kind === "pace") {
                sendPlasmaNotification(
                    i18n("%1 pace warning", item.title),
                    i18n("%1 may run out in %2", row.label, paceEtaText(row.paceEtaSeconds)),
                    "normal")
            } else if (intent.kind === "reset") {
                sendPlasmaNotification(
                    i18n("%1 limit reset", item.title),
                    i18n("%1 is back to %2% used", row.label, Math.round(row.usedPercent)),
                    "low")
            }
        }
    }

    function processNotifications() {
        if (!enableNotifications || providers.length === 0) {
            return
        }
        var observations = notificationObservations()
        var mode = notificationsPrimed ? "observe" : "prime"
        var result = NotificationPlanner.transition(
            observations,
            notificationMemo,
            notificationPlannerOptions(mode))
        // Commit the full transition before running any external effect. A
        // re-entrant refresh cannot observe the old baseline and notify twice.
        notificationMemo = result.nextMemo
        notificationsPrimed = true
        dispatchNotificationIntents(result.intents, observations)
    }

    function notificationRank(severity) {
        return NotificationMemo.severityRank(severity)
    }

    function notificationUrgency(severity) {
        switch (String(severity || "")) {
        case "critical":
        case "major":
            return "critical"
        case "unknown":
            return "low"
        default:
            return "normal"
        }
    }

    function sendPlasmaNotification(title, body, urgency) {
        var cleanTitle = privacyMode ? "CodexBar" : String(title || "CodexBar").trim()
        var cleanBody = privacyMode ? i18n("Usage or status changed. Open CodexBar for details.") : String(body || "").trim()
        var cleanUrgency = String(urgency || "normal").trim()
        if (cleanTitle.length === 0) {
            cleanTitle = "CodexBar"
        }
        if (cleanUrgency !== "low" && cleanUrgency !== "normal" && cleanUrgency !== "critical") {
            cleanUrgency = "normal"
        }
        var command = "if command -v notify-send >/dev/null 2>&1; then notify-send --app-name=CodexBar --icon=view-statistics --urgency="
            + shellQuote(cleanUrgency) + " -- " + shellQuote(cleanTitle) + " " + shellQuote(cleanBody) + "; fi"
        // A shell assignment cannot directly prefix the reserved word `if`.
        connectNotificationCommand(commandWithRunNonce(":; " + command))
    }

    function notifyAvailableUpdate(version, url) {
        if (!enableNotifications || !updateNotificationsEnabled) {
            return
        }
        var cleanVersion = String(version || "").trim()
        var memoKey = cleanVersion.length > 0 ? cleanVersion : url
        if (memoKey.length === 0 || memoKey === lastNotifiedUpdateVersion) {
            return
        }
        lastNotifiedUpdateVersion = memoKey
        Plasmoid.configuration.lastNotifiedUpdateVersion = memoKey
        var title = i18n("CodexBar widget update available")
        var body = cleanVersion.length > 0
            ? i18n("Version %1 is available.", cleanVersion)
            : i18n("A new widget version is available.")
        sendPlasmaNotification(title, body, "normal")
    }

    function notifyInstalledUpdate(version) {
        if (!enableNotifications || !updateNotificationsEnabled) {
            return
        }
        var cleanVersion = String(version || "").trim()
        var title = i18n("CodexBar widget update installed")
        var restartText = i18n("Restart Plasma to apply the new widget version.")
        var body = cleanVersion.length > 0
            ? i18n("Version %1 was installed. %2", cleanVersion, restartText)
            : i18n("A widget update was installed. %1", restartText)
        sendPlasmaNotification(title, body, "normal")
    }

    function planText(providerID, usage, item) {
        var identity = usage.identity || ({})
        var method = identity.loginMethod || usage.loginMethod || ""
        if (providerKey(providerID) === "codex" && method.length > 0) {
            return capitalize(method)
        }
        return ""
    }

    function providerKey(value) {
        return ProviderIdentity.resolveProviderKey(value)
    }

    function providerCliArgument(value) {
        return ProviderIdentity.providerCliArgument(value)
    }

    function providerDisplayTitle(value) {
        return providerTitle(value, "", privacyMode)
    }

    function providerTitle(value, displayName, privateTitle) {
        var key = providerKey(value)
        var preferred = privateTitle === true ? "" : String(displayName || "").trim()
        if (preferred.length > 0) {
            return preferred
        }

        var names = {
            "abacus": i18n("Abacus AI"),
            "aiand": i18n("ai&"),
            "alibaba": i18n("Alibaba"),
            "alibabatokenplan": i18n("Alibaba Token Plan"),
            "amp": i18n("Amp"),
            "antigravity": i18n("Antigravity"),
            "augment": i18n("Augment"),
            "azureopenai": i18n("Azure OpenAI"),
            "bedrock": i18n("AWS Bedrock"),
            "chutes": i18n("Chutes"),
            "claude": i18n("Claude"),
            "clawrouter": i18n("ClawRouter"),
            "clinepass": i18n("ClinePass"),
            "codebuff": i18n("Codebuff"),
            "codex": i18n("Codex"),
            "commandcode": i18n("Command Code"),
            "copilot": i18n("Copilot"),
            "crof": i18n("Crof"),
            "crossmodel": i18n("CrossModel"),
            "cursor": i18n("Cursor"),
            "deepgram": i18n("Deepgram"),
            "deepinfra": i18n("DeepInfra"),
            "deepseek": i18n("DeepSeek"),
            "devin": i18n("Devin"),
            "doubao": i18n("Doubao"),
            "elevenlabs": i18n("ElevenLabs"),
            "factory": i18n("Droid"),
            "fireworks": i18n("Fireworks"),
            "gemini": i18n("Gemini"),
            "grok": i18n("Grok"),
            "groq": i18n("Groq"),
            "ibmbob": i18n("IBM Bob"),
            "jetbrains": i18n("JetBrains AI"),
            "kilo": i18n("Kilo"),
            "kimi": i18n("Kimi Code"),
            "kimik2": i18n("Kimi K2 (unofficial)"),
            "kiro": i18n("Kiro"),
            "litellm": i18n("LiteLLM"),
            "llmproxy": i18n("LLM Proxy"),
            "longcat": i18n("LongCat"),
            "manus": i18n("Manus"),
            "mimo": i18n("Xiaomi MiMo"),
            "minimax": i18n("MiniMax"),
            "mistral": i18n("Mistral"),
            "moonshot": i18n("Moonshot / Kimi Open Platform"),
            "neuralwatt": i18n("Neuralwatt"),
            "notion": i18n("Notion AI"),
            "ollama": i18n("Ollama"),
            "openai": i18n("OpenAI"),
            "opencode": i18n("OpenCode"),
            "opencodego": i18n("OpenCode Go"),
            "openrouter": i18n("OpenRouter"),
            "perplexity": i18n("Perplexity"),
            "poe": i18n("Poe"),
            "qoder": i18n("Qoder"),
            "qwencloud": i18n("Qwen Cloud"),
            "sakana": i18n("Sakana AI"),
            "stepfun": i18n("StepFun"),
            "sub2api": i18n("sub2api"),
            "synthetic": i18n("Synthetic"),
            "t3chat": i18n("T3 Chat"),
            "venice": i18n("Venice"),
            "vertexai": i18n("Vertex AI"),
            "warp": i18n("Warp"),
            "wayfinder": i18n("Wayfinder"),
            "windsurf": i18n("Windsurf"),
            "xai": i18n("xAI"),
            "zai": i18n("z.ai / GLM"),
            "zed": i18n("Zed"),
            "zenmux": i18n("ZenMux"),
            "zoommate": i18n("ZoomMate")
        }

        if (hasOwnKey(names, key)) {
            return names[key]
        }

        if (privateTitle === true) {
            return i18n("Provider")
        }
        var words = String(key).replace(/[_-]/g, " ").split(" ")
        for (var i = 0; i < words.length; i++) {
            if (words[i].length > 0) {
                words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1)
            }
        }
        return words.join(" ")
    }

    function providerIconSource(value) {
        var fileName = ProviderIdentity.providerIconFileName(value)
        if (fileName.length === 0) {
            return "view-statistics"
        }
        return Qt.resolvedUrl("../icons/providers/" + fileName)
    }

    function providerIconIsMask(value) {
        return true
    }

    // Whether the rendered icon can stand in for the provider's name. Bundled
    // icons and brand colors cover the same providers, so a provider outside
    // that set falls back to one generic icon in the theme highlight and is
    // indistinguishable from every other provider outside it.
    function providerIconIdentifies(value) {
        return ProviderIdentity.providerBrandColorChannels(value).length === 3
    }

    function providerColor(value) {
        var channels = ProviderIdentity.providerBrandColorChannels(value)
        if (channels.length !== 3) {
            return Kirigami.Theme.highlightColor
        }
        return Qt.rgba(channels[0], channels[1], channels[2], 1)
    }

    function providerDashboardUrl(providerID) {
        return ProviderIdentity.providerDashboardUrl(providerID)
    }

    function providerDocsUrl(providerID) {
        return ProviderIdentity.providerDocsUrl(providerID)
    }

    function providerLoginUrl(providerID) {
        return ProviderIdentity.providerLoginUrl(providerID)
    }

    function providerStatusUrl(providerID) {
        return ProviderIdentity.providerStatusUrl(providerID)
    }

    function safeStatusUrl(providerID, url) {
        return Normalizer.safeStatusUrl(providerStatusUrl(providerID), url)
    }

    function providerChangelogUrl(providerID) {
        switch (providerKey(providerID)) {
        case "codex":
            return "https://github.com/openai/codex/releases"
        case "claude":
            return "https://github.com/anthropics/claude-code/releases"
        case "gemini":
            return "https://github.com/google-gemini/gemini-cli/releases"
        case "grok":
            return "https://x.ai/news"
        default:
            return ""
        }
    }

    function actionRows(item) {
        if (!item) {
            return []
        }

        // This menu uses raw provider capabilities and account presence only.
        // Titles are local literals; never render identity or provider prose here.
        var rows = []
        rows.push({
            title: accountLoadingForProvider(item.provider) ? i18n("Loading accounts...") : i18n("Accounts..."),
            icon: "user-identity",
            action: "accounts",
            enabled: !accountLoadingForProvider(item.provider)
        })

        var accountAction = providerAccountAction(item)
        if (accountAction) {
            rows.push(accountAction)
        }

        if (item.dashboardUrl && item.dashboardUrl.length > 0) {
            rows.push({ title: i18n("Usage Dashboard"), icon: "view-statistics", action: "dashboard", enabled: true })
        }
        if (safeStatusUrl(item.provider, item.statusUrl).length > 0) {
            rows.push({ title: i18n("Status Page"), icon: "network-connect", action: "status", enabled: true })
        }
        if (showProviderChangelogs && item.changelogUrl && item.changelogUrl.length > 0) {
            rows.push({ title: i18n("Changelog"), icon: "view-list-details", action: "changelog", enabled: true })
        }
        var docsUrl = providerDocsUrl(item.provider)
        if (docsUrl.length > 0) {
            rows.push({ title: i18n("Docs"), icon: "help-contents", action: "docs", url: docsUrl, enabled: true })
        }

        rows.push({ title: i18n("Refresh"), icon: "view-refresh", action: "refresh", enabled: true, separatorBefore: true })
        rows.push({ title: i18n("Settings..."), icon: "configure", action: "settings", enabled: true })
        rows.push({ title: i18n("About CodexBar"), icon: "help-about", action: "about", enabled: true })
        return rows
    }

    function providerAccountAction(item) {
        var title = item.account && item.account.length > 0 ? i18n("Switch Account...") : i18n("Add Account...")
        var loginUrl = providerLoginUrl(item.provider)
        switch (providerKey(item.provider)) {
        case "devin":
            return { title: i18n("Open Devin..."), icon: "internet-services", action: "account-url", url: "https://app.devin.ai/settings/usage", enabled: true }
        case "factory":
            return { title: i18n("Open Droid in Browser..."), icon: "internet-services", action: "account-url", url: "https://app.factory.ai", enabled: true }
        case "manus":
            return { title: title, icon: "internet-services", action: "account-url", url: "https://manus.im", enabled: true }
        case "mimo":
            return { title: title, icon: "internet-services", action: "account-url", url: "https://platform.xiaomimimo.com/api/v1/genLoginUrl?currentPath=%2F%23%2Fconsole%2Fbalance", enabled: true }
        case "perplexity":
            return { title: title, icon: "internet-services", action: "account-url", url: "https://www.perplexity.ai/", enabled: true }
        default:
            return loginUrl.length > 0
                ? { title: title, icon: "internet-services", action: "account-url", url: loginUrl, enabled: true }
                : null
        }
    }

    function performAction(actionRow) {
        var actionID = actionRow && actionRow.action ? actionRow.action : actionRow
        var item = selectedProviderData
        if (actionID === "dashboard" && item) {
            Qt.openUrlExternally(item.dashboardUrl)
        } else if (actionID === "status" && item) {
            Qt.openUrlExternally(safeStatusUrl(item.provider, item.statusUrl))
        } else if (actionID === "changelog" && item) {
            Qt.openUrlExternally(item.changelogUrl)
        } else if (actionID === "docs" && actionRow && actionRow.url) {
            Qt.openUrlExternally(actionRow.url)
        } else if (actionID === "accounts" && item) {
            root.loadAccounts(item.provider)
        } else if (actionID === "account-url" && actionRow && actionRow.url) {
            Qt.openUrlExternally(actionRow.url)
        } else if (actionID === "refresh") {
            root.refreshNow(true)
        } else if (actionID === "about") {
            Qt.openUrlExternally("https://github.com/steipete/CodexBar")
        } else if (actionID === "settings") {
            var action = Plasmoid.internalAction("configure")
            if (action) {
                action.trigger()
            }
        }
    }

    function withAlpha(color, alpha) {
        return Qt.rgba(color.r, color.g, color.b, alpha)
    }

    function canvasColor(color, alpha) {
        var opacity = alpha === undefined ? color.a : alpha
        return "rgba("
            + Math.round(color.r * 255) + ", "
            + Math.round(color.g * 255) + ", "
            + Math.round(color.b * 255) + ", "
            + opacity + ")"
    }

    function contrastTextColor(color) {
        var luminance = (0.2126 * color.r) + (0.7152 * color.g) + (0.0722 * color.b)
        return luminance > 0.62 ? Qt.rgba(0.08, 0.08, 0.1, 1) : Qt.rgba(1, 1, 1, 1)
    }

    function readableAccentColor(accent, background) {
        var surface = background || Kirigami.Theme.backgroundColor
        return ThemeContrast.readableAccentColor(
            accent,
            surface,
            Kirigami.Theme.textColor)
    }

    function providerReadableColor(value, background) {
        return readableAccentColor(
            providerColor(value),
            background || Kirigami.Theme.backgroundColor)
    }

    function copyObject(item) {
        return Guards.copyObject(item)
    }

    function hasAdditionalSections(item) {
        return item && ((showPopupCredits && (item.credits !== null || item.codexCreditLimit || item.resetCredits))
            || (showPopupProviderDetails && (item.usageDashboard || item.providerCost
                || (item.providerDetails && item.providerDetails.length > 0)))
            || item.tokenCost) ? true : false
    }

    function capitalize(value) {
        var text = String(value || "")
        if (text.length === 0) {
            return ""
        }
        return text.charAt(0).toUpperCase() + text.slice(1)
    }

    function localizedPeriod(value) {
        var text = String(value || "").trim()
        switch (text.toLowerCase()) {
        case "last 30 days":
            return i18n("Last 30 days")
        case "this month":
            return i18n("This month")
        case "today":
            return i18n("Today")
        default:
            return text
        }
    }

    // Four- and five-figure totals are unreadable as a bare digit run, and
    // Number.toLocaleString with 'f' localizes the decimal mark without adding
    // group separators, so the grouping is applied here.
    function groupedDecimalString(value, digits) {
        return CostPresentation.groupedDecimalString(costNumberFormat, value, digits)
    }

    function amountString(value, currency) {
        return CostPresentation.amountString(costNumberFormat, value, currency)
    }

    // Same figures as costLine without the window label, for surfaces that
    // already state the range once (the Usage & Spend range selector).
    function qualifiedCostValue(value, valueMode) {
        switch (valueMode) {
        case "estimated":
            return i18n("%1 (estimated)", value)
        case "partial":
            return i18n("%1 (partial)", value)
        case "approximate":
            return i18n("%1 (approximate)", value)
        default:
            return value
        }
    }

    function costValueLine(cost, tokens, currency, valueMode) {
        var numericCost = Normalizer.strictFiniteNumber(cost)
        var numericTokens = Normalizer.strictFiniteNumber(tokens)
        var hasCost = isFinite(numericCost)
        var costValue = hasCost ? amountString(numericCost, currency) : "-"
        if (hasCost) {
            costValue = qualifiedCostValue(costValue, valueMode)
        }
        if (isFinite(numericTokens)) {
            return i18n("%1 - %2", costValue, usageCountText(numericTokens, "tokens"))
        }
        return costValue
    }

    function costLine(label, cost, tokens, currency, valueMode) {
        var numericCost = Normalizer.strictFiniteNumber(cost)
        var numericTokens = Normalizer.strictFiniteNumber(tokens)
        var hasCost = isFinite(numericCost)
        var costValue = hasCost ? amountString(numericCost, currency) : "-"
        if (hasCost) {
            costValue = qualifiedCostValue(costValue, valueMode)
        }
        if (isFinite(numericTokens)) {
            return i18n("%1: %2", label,
                i18n("%1 - %2", costValue, usageCountText(numericTokens, "tokens")))
        }
        return i18n("%1: %2", label, costValue)
    }

    function tokenCountString(tokens) {
        return CostPresentation.tokenCountString(tokens)
    }

    function usageCountText(value, unit) {
        var text = CostPresentation.tokenCountString(value)
        var count = Number(text)
        // Compact counts such as 1K keep their own translation. Only the small
        // displayed integers go through KI18n's integer plural argument.
        var exact = isFinite(count)
        switch (unit) {
        case "tokens":
            return exact ? i18np("%1 token", "%1 tokens", count) : i18n("%1 tokens", text)
        case "requests":
            return exact ? i18np("%1 request", "%1 requests", count) : i18n("%1 requests", text)
        case "points":
            return exact ? i18np("%1 point", "%1 points", count) : i18n("%1 points", text)
        default:
            return text
        }
    }

    function tokenCostHint(providerID) {
        switch (providerKey(providerID)) {
        case "antigravity":
            return i18n("Local Antigravity history includes token totals. Dollar costs are unavailable.")
        case "codex":
            return i18n("Estimated from local Codex logs for the selected account.")
        case "claude":
            return i18n("Estimated from local Claude logs.")
        default:
            return ""
        }
    }

    function usageRowForLane(item, lane) {
        if (!item || !item.rows) {
            return null
        }
        for (var i = 0; i < item.rows.length; i++) {
            if (item.rows[i] && item.rows[i].lane === lane) {
                return item.rows[i]
            }
        }
        return null
    }

    function appendUniqueUsageRow(rows, row) {
        if (row && rows.indexOf(row) === -1) {
            rows.push(row)
        }
    }

    // Preserve each provider's compact-row preference, then let PanelDisplay
    // choose by capability. A pace or reset mode must not discard a useful row
    // merely because that row has no percentage.
    function switcherCandidateRows(item) {
        if (!item || !item.rows || item.rows.length === 0) {
            return []
        }

        var key = providerKey(item.provider)
        var primary = usageRowForLane(item, "primary")
        var secondary = usageRowForLane(item, "secondary")
        var tertiary = usageRowForLane(item, "tertiary")
        var preferred = []
        if (key === "factory") {
            appendUniqueUsageRow(preferred, secondary)
            appendUniqueUsageRow(preferred, primary)
        } else if (key === "perplexity" && primary && primary.hasPercent
                && primary.leftPercent <= 0) {
            appendUniqueUsageRow(preferred, secondary)
            appendUniqueUsageRow(preferred, tertiary)
            appendUniqueUsageRow(preferred, primary)
        } else {
            appendUniqueUsageRow(preferred, primary)
            appendUniqueUsageRow(preferred, secondary)
            appendUniqueUsageRow(preferred, tertiary)
        }
        if (key === "cursor" && primary && primary.hasPercent && primary.leftPercent <= 0
                && item.providerCost && item.providerCost.percentUsed >= 0) {
            var used = clamp(Number(item.providerCost.percentUsed), 0, 100)
            preferred.unshift({
                lane: "providerCost",
                label: i18n("Included plan"),
                hasPercent: true,
                usedPercent: used,
                leftPercent: clamp(100 - used, 0, 100),
                pacePercent: -1,
                paceOnTop: true,
                reset: "",
                pace: ""
            })
        }

        for (var i = 0; i < item.rows.length; i++) {
            appendUniqueUsageRow(preferred, item.rows[i])
        }
        return preferred
    }

    function panelDisplayRow(item, mode) {
        return PanelDisplay.rowForMode(switcherCandidateRows(item), mode, panelQuotaLane)
    }

    function panelMeterRows(item) {
        return PanelDisplay.meterRows(switcherCandidateRows(item), panelQuotaLane)
    }

    function panelMeterDescription(item) {
        var description = panelMeterRows(item).map(function(row) {
            var text = i18n("%1: %2% %3", row.label, Math.round(displayPercent(row)),
                usageBarsShowUsed ? i18n("used") : i18n("left"))
            var reset = resetTextForRow(row)
            return reset.length > 0 ? i18n("%1 - %2", text, reset) : text
        }).join(". ")
        if (!item || item.usageStale !== true) {
            return description
        }
        var lastKnown = lastGoodUsageText(item)
        return description.length > 0 ? i18n("%1 - %2", description, lastKnown) : lastKnown
    }

    function switcherMetricRow(item) {
        // Popup tabs retain their automatic quota independently of panel settings.
        return PanelDisplay.rowForMode(switcherCandidateRows(item), "percent")
    }

    function switcherPercent(item) {
        var row = switcherMetricRow(item)
        return row ? displayPercent(row) : -1
    }

    function isOverviewErrorOnly(item) {
        return item
            && item.error
            && item.error.length > 0
            && (!item.rows || item.rows.length === 0)
            && providerPlaceholderText(item).length === 0
            && item.credits === null
            && item.codexCreditLimit === null
            && !item.resetCredits
            && !item.providerCost
            && !item.tokenCost
    }

    function overviewProviders() {
        var eligible = []
        if (!providers) {
            return eligible
        }
        for (var i = 0; i < providers.length; i++) {
            if (!isOverviewErrorOnly(providers[i])) {
                eligible.push(providers[i])
            }
        }

        var configured = configuredOverviewProviderIDs()
        if (String(overviewProviderIDsRaw || "").trim().length === 0) {
            return eligible.slice(0, maxOverviewProviders)
        }
        if (configured.length === 0) {
            return []
        }

        var selected = ({})
        for (var j = 0; j < configured.length; j++) {
            selected[configured[j]] = true
        }

        var result = []
        for (var k = 0; k < eligible.length; k++) {
            if (hasOwnKey(selected, String(eligible[k].provider))) {
                result.push(eligible[k])
                if (result.length >= maxOverviewProviders) {
                    break
                }
            }
        }
        return result
    }

    // The settings page stores canonical provider IDs (legacy raw CLI
    // spellings such as groqcloud still resolve); parsing is shared with
    // configPopup.qml through OverviewProviders so the checkboxes cannot
    // drift from this selection.
    function configuredOverviewProviderIDs() {
        return OverviewProviders.configuredProviderIDs(overviewProviderIDsRaw)
    }

    function providerIndex(item) {
        return item ? providerIndexForID(item.provider) : -1
    }

    function providerIndexForID(providerID) {
        var id = String(providerID || "")
        if (id.length === 0 || !providers) {
            return -1
        }
        for (var i = 0; i < providers.length; i++) {
            if (providers[i] && providers[i].provider === id) {
                return i
            }
        }
        return -1
    }

    function openProviderFromPanel(providerID) {
        var index = providerIndexForID(providerID)
        if (index < 0) {
            return false
        }
        selectedProviderID = providers[index].provider
        selectionInitialized = true
        expanded = true
        return true
    }

    function overviewDetailText(item) {
        if (!item) {
            return ""
        }
        if (item.account && item.account.length > 0) {
            return item.account
        }
        // Only an active incident may stand in for a missing account: an
        // operational status is service state, not identity information.
        if (item.hasIncident === true && item.statusKnown !== false
                && item.status && item.status.length > 0) {
            return item.status
        }
        var placeholder = providerPlaceholderText(item)
        if (placeholder.length > 0) {
            return placeholder
        }
        if (item.source && item.source.length > 0) {
            var source = String(item.source).trim().toLowerCase()
            var providerID = String(item.provider || "").trim().toLowerCase()
            var title = String(item.title || "").trim().toLowerCase()
            // Repeating the provider's own name adds nothing under its title.
            if (source !== providerID && source !== title) {
                return item.source
            }
        }
        return ""
    }

    function providerPlaceholderText(item) {
        if (!item || !item.placeholder || item.placeholder.length === 0) {
            return ""
        }
        if (item.provider === "codex" && item.tokenCost) {
            return ""
        }
        return item.placeholder
    }

    function displayPercent(row) {
        if (!row || !row.hasPercent) {
            return 0
        }
        return usageBarsShowUsed ? row.usedPercent : row.leftPercent
    }

    function paceMarkerPercent(row) {
        if (!row || row.pacePercent < 0) {
            return -1
        }
        return usageBarsShowUsed ? row.pacePercent : clamp(100 - row.pacePercent, 0, 100)
    }

    function percentSuffix() {
        return usageBarsShowUsed ? i18n("used") : i18n("left")
    }

    function resetLabel(value) {
        var text = String(value || "").trim()
        if (text.length === 0) {
            return ""
        }
        // Only split where a unit letter runs into the next number
        // ("Resets5h30m" -> "Resets 5h 30m"). Splitting digit-then-letter as
        // well would also tear a number away from its own unit and render our
        // own compact durations ("2h 30m") as "2 h 30 m".
        text = text
            .replace(/([A-Za-z])(\d)/g, "$1 $2")
            .replace(/\)([A-Za-z])/g, ") $1")
            .replace(/(am|pm)\(/ig, "$1 (")
            .replace(/\s+/g, " ")
        if (/^resets\b/i.test(text)) {
            var rest = text.replace(/^resets\s*/i, "")
            return resetLabelLooksLikeTime(rest) ? i18n("Resets %1", rest) : rest
        }
        return resetLabelLooksLikeTime(text) ? i18n("Resets %1", text) : text
    }

    function resetLabelLooksLikeTime(value) {
        var text = String(value || "").trim()
        if (text.length === 0) {
            return false
        }
        if (/^(now|today|tomorrow)\b/i.test(text)) {
            return true
        }
        if (/^\d{1,2}(:\d{2})?\s*(am|pm)(\s*\([^)]+\))?$/i.test(text)) {
            return true
        }
        if (/^\d{1,2}:\d{2}(\s*\([^)]+\))?$/.test(text)) {
            return true
        }
        if (/^\S+\s+\d{1,2}:\d{2}(\s*\([^)]+\))?$/.test(text)) {
            return true
        }
        return /^\d+\s*(min|m|h|hr|hour|hours|d|day|days)(\s+\d+\s*(min|m|h|hr|hour|hours|d|day|days))*$/i.test(text)
    }

    function providerCountText(count) {
        var total = Math.max(0, Math.round(Number(count) || 0))
        return i18np("%1 provider", "%1 providers", total)
    }

    function clamp(value, minimum, maximum) {
        return Normalizer.clamp(value, minimum, maximum)
    }

    function primaryProvider() {
        return providers.length > 0 ? providers[0] : null
    }

    function selectedCompactProvider() {
        if (providers.length === 0) {
            return null
        }
        var panelItems = panelProviderItems()
        var selected = selectedProviderIndex >= 0 ? providers[selectedProviderIndex] : null
        return PopupSelection.compactPanelProvider(
            autoSelectProvider, selected, panelItems,
            autoSelectedProviderIndex(panelItems))
    }

    function globalViewAvailability() {
        return {
            overview: overviewAvailable,
            spend: spendAvailable,
            sessions: sessionsAvailable
        }
    }

    function selectGlobalView(viewID) {
        var candidate = String(viewID || "")
        if (!PopupSelection.globalViewIsAvailable(candidate, globalViewAvailability())) {
            return
        }

        selectedGlobalView = candidate
        selectedProviderID = ""
        selectionInitialized = true
        if (candidate === "sessions") {
            refreshSessionsIfStale()
        }
    }

    function updateSelectedProvider() {
        var firstProviderID = providers && providers.length > 0
            ? providers[0].provider
            : ""
        var automaticProviderID = firstProviderID.length > 0
            ? providers[autoSelectedProviderIndex(providers)].provider
            : ""
        var next = PopupSelection.reconcile({
            providerID: selectedProviderID,
            globalView: selectedGlobalView,
            initialized: selectionInitialized
        }, {
            autoSelect: autoSelectProvider,
            currentProviderExists: selectedProviderIndex >= 0,
            firstProviderID: firstProviderID,
            automaticProviderID: automaticProviderID,
            globalViews: globalViewAvailability()
        })
        selectedProviderID = next.providerID
        selectedGlobalView = next.globalView
        selectionInitialized = next.initialized
    }

    function reconcileGlobalViewAvailability() {
        if (PopupSelection.globalSelectionNeedsReconciliation({
            providerID: selectedProviderID,
            globalView: selectedGlobalView,
            initialized: selectionInitialized
        }, globalViewAvailability())) {
            updateSelectedProvider()
        }
    }

    function autoSelectedProviderIndex(items) {
        var source = Array.isArray(items) ? items : providers
        var bestIndex = 0
        var bestScore = -1
        for (var i = 0; i < source.length; i++) {
            var score = autoSelectScore(source[i])
            if (score > bestScore) {
                bestScore = score
                bestIndex = i
            }
        }
        return bestIndex
    }

    function autoSelectScore(item) {
        if (!item || isOverviewErrorOnly(item)) {
            return -1
        }
        var percent = autoSelectUsedPercent(item)
        var incidentTieBreaker = notificationRank(item.statusSeverity) / 100
        return percent >= 0 ? percent + incidentTieBreaker : incidentTieBreaker
    }

    function autoSelectUsedPercent(item) {
        if (!item) {
            return -1
        }

        var best = -1
        var rows = item.rows || []
        for (var i = 0; i < rows.length; i++) {
            if (rows[i] && rows[i].hasPercent) {
                var used = Number(rows[i].usedPercent)
                if (isFinite(used)) {
                    best = Math.max(best, clamp(used, 0, 100))
                }
            }
        }
        if (item.providerCost && item.providerCost.percentUsed >= 0) {
            var providerCostUsed = Number(item.providerCost.percentUsed)
            if (isFinite(providerCostUsed)) {
                best = Math.max(best, clamp(providerCostUsed, 0, 100))
            }
        }
        return best
    }

    // The panel-only provider selection. The popup, notifications, and every
    // other surface keep the full roster; only compact rendering narrows here.
    function panelProviderItems() {
        return PanelProviders.filteredItems(providers, panelProviderIDsRaw)
    }

    function compactProviders() {
        if (!providers || Plasmoid.configuration.showMultiProviderInPanel === false) {
            return []
        }

        var result = []
        var panelItems = panelProviderItems()
        for (var i = 0; i < panelItems.length && result.length < PanelProviders.maximumSelectableProviders; i++) {
            var rows = panelMeterRows(panelItems[i])
            if (PanelRules.matchesAny(panelVisibilityRules.meters, rows, panelClockMs)) {
                result.push(providerPresentation(panelItems[i]))
            }
        }
        return result
    }

    // The panel label as separable segments. The compact renderer surrenders
    // whole segments when the meter row leaves it too little room, so it needs
    // them apart; every other caller joins them back through compactText().
    function compactTextSegments() {
        var item = providerPresentation(selectedCompactProvider())
        var row = panelDisplayRow(item, menuBarDisplayMode)
        if (!PanelRules.matches(panelVisibilityRules.text, row, panelClockMs)) {
            return []
        }
        if (!item) {
            if (PanelProviders.selectionActive(panelProviderIDsRaw)) {
                return []
            }
            // The standalone identity fallback names the widget itself, so it
            // carries no droppable segment: it is the usage slot or nothing.
            return [{ id: "usage", text: loading ? i18n("Loading") : "CodexBar" }]
        }

        var segments = []
        if (Plasmoid.configuration.showProviderInPanel) {
            segments.push({ id: "name", text: item.title,
                identifying: !providerIconIdentifies(item.provider) })
        }

        var display = menuBarDisplayText(item)
        if (Plasmoid.configuration.showPercentInPanel && display.length > 0) {
            segments.push({ id: "usage", text: display })
        }

        if (Plasmoid.configuration.showCreditsInPanel && item.credits !== null) {
            segments.push({ id: "credits", text: i18n("%1cr", formatNumber(item.credits)) })
        }

        return segments
    }

    function compactText() {
        return PanelTextFit.fullText(compactTextSegments())
    }

    function setHoveredPanelProvider(providerID) {
        hoveredPanelProviderID = String(providerID || "")
    }

    function clearHoveredPanelProvider(providerID) {
        var id = String(providerID || "")
        if (hoveredPanelProviderID === id) {
            hoveredPanelProviderID = ""
        }
    }

    function hoveredPanelProvider() {
        var id = String(hoveredPanelProviderID || "")
        if (id.length === 0 || !providers) {
            return null
        }
        // Visibility rules can filter a hovered meter out of the rendered
        // compactProviders() while its provider stays in providers, and its
        // destroyed MouseArea delivers no reliable hover-exit for cleanup.
        // Narrow the tooltip only while the meter is actually rendered.
        var rendered = compactProviders().some(function(meter) {
            return meter && meter.provider === id
        })
        if (!rendered) {
            return null
        }
        for (var i = 0; i < providers.length; i++) {
            if (providers[i] && providers[i].provider === id) {
                return providers[i]
            }
        }
        return null
    }

    function panelProviderToolTipText(item) {
        var presented = providerPresentation(item)
        if (!presented) {
            return ""
        }
        // Keep incidents in the tooltip even when quota meters are available.
        var incident = presented.hasIncident && presented.statusKnown !== false && presented.status.length > 0 ? presented.status : ""
        var details = []
        var description = panelMeterDescription(presented)
        if (description.length > 0) {
            details.push(description)
        }
        // A crowded panel surrenders the credit balance before the usage
        // figure, and no meter carries it, so the tooltip is where a pointer
        // user recovers what the panel had no room to draw.
        if (Plasmoid.configuration.showCreditsInPanel && presented.credits !== null
                && presented.credits !== undefined) {
            details.push(i18n("%1cr", formatNumber(presented.credits)))
        }
        if (details.length > 0) {
            var line = i18n("%1: %2", presented.title, details.join(". "))
            if (incident.length > 0) {
                return i18n("%1 - %2", line, incident)
            }
            return line
        }
        if (incident.length > 0) {
            return i18n("%1: %2", presented.title, incident)
        }
        return ""
    }

    function panelToolTipText() {
        var hovered = hoveredPanelProvider()
        if (hovered) {
            var hoveredLine = panelProviderToolTipText(hovered)
            if (hoveredLine.length > 0) {
                return hoveredLine
            }
        }
        var lines = []
        var roster = panelProviderItems()
        for (var i = 0; i < roster.length && lines.length < 6; i++) {
            var line = panelProviderToolTipText(roster[i])
            if (line.length > 0) {
                lines.push(line)
            }
        }
        if (loading) {
            lines.push(i18n("Refreshing usage..."))
        }
        if (lines.length === 0 && errorText.length > 0) {
            return privateErrorText(Normalizer.boundedDisplayText(errorText, 500))
        }
        return lines.join("\n")
    }

    function menuBarDisplayText(item) {
        if (!item) {
            return ""
        }

        var mode = String(menuBarDisplayMode || "percent")
        var row = panelDisplayRow(item, mode)
        if (mode === "pace") {
            return paceTextForRow(row)
        }
        if (mode === "both") {
            var percentText = percentTextForRow(row)
            var paceText = paceTextForRow(row)
            if (percentText.length > 0 && paceText.length > 0) {
                return i18n("%1 - %2", percentText, paceText)
            }
            return percentText.length > 0 ? percentText : paceText
        }
        if (mode === "resetTime") {
            return resetTextForRow(row)
        }
        if (mode === "runOut") {
            return runOutTextForRow(row)
        }
        return percentTextForRow(row)
    }

    function percentTextForRow(row) {
        if (!row || !row.hasPercent) {
            return ""
        }
        return i18n("%1% %2", Math.round(displayPercent(row)), percentSuffix())
    }

    function paceTextForRow(row) {
        if (!row || row.pacePercent < 0) {
            return ""
        }
        var shownPace = paceMarkerPercent(row)
        if (shownPace < 0) {
            return ""
        }
        return row.paceOnTop
            ? i18n("%1% %2 at pace", Math.round(shownPace), percentSuffix())
            : i18n("%1% %2, behind pace", Math.round(shownPace), percentSuffix())
    }

    // Duration-only forecast token. It stays empty unless the CLI actually
    // predicts exhaustion before the reset, so the panel never shows a
    // countdown the pace data does not support.
    function runOutTextForRow(row) {
        if (!paceWarningActive(row)) {
            return ""
        }
        return paceEtaText(PanelDisplay.remainingSeconds(
            row.paceEtaSeconds, usageSnapshotReceivedAtMs, panelClockMs))
    }

    function resetTextForRow(row) {
        var reset = usageResetText(row)
        if (reset.length === 0) {
            return ""
        }
        return resetLabel(reset)
    }

    // Credit balances are plain counts, so they share the popup's grouped,
    // locale-aware figure formatting instead of printing a bare digit run with
    // a hardcoded decimal mark. A whole balance keeps no fractional part, so a
    // depleted account reads as "0" rather than "0.0".
    function formatNumber(value) {
        return CostPresentation.formatCount(costNumberFormat, value)
    }

    Plasma5Support.DataSource {
        id: usageSource

        engine: "executable"
        interval: 0

        onNewData: function(sourceName, data) {
            var rawStdoutText = data && data["stdout"] ? data["stdout"] : ""
            var stdoutText = SafeText.cliJsonText(rawStdoutText)
            var stderrText = data && data["stderr"] ? data["stderr"] : ""
            if (stdoutText === null) {
                stdoutText = ""
                stderrText = i18n("codexbar response exceeded the supported size.")
            }

            // A reply the ledger no longer holds is a late result from a
            // retired run. Dropping it is what keeps it from overwriting the
            // refresh that replaced it.
            var descriptor = CommandLedger.find(root.activeCommandDescriptors, sourceName)
            if (!descriptor) {
                return
            }

            switch (descriptor.kind) {
            case "cost":
                var requestedHistoryDays = descriptor.costHistoryDays !== undefined
                    ? descriptor.costHistoryDays
                    : root.costHistoryDays
                root.finishUsageCommandSource(sourceName)
                root.parseCostOutput(stdoutText, stderrText, requestedHistoryDays)
                return
            case "sessions":
                root.finishUsageCommandSource(sourceName)
                root.parseSessionsOutput(stdoutText, stderrText)
                root.scheduleSessionsRefreshCheck()
                return
            case "providerConfig":
                root.finishUsageCommandSource(sourceName)
                root.parseProviderConfigOutput(descriptor, stdoutText, stderrText)
                return
            case "account":
                root.parseProviderAccountsOutput(sourceName, descriptor, stdoutText, stderrText)
                return
            case "providerFallback":
                root.parseProviderFallbackOutput(
                    sourceName, descriptor.providerID, stdoutText, stderrText)
                return
            case "usage":
                root.finishUsageCommandSource(sourceName)
                root.parseOutput(stdoutText, stderrText)
                return
            default:
                root.finishUsageCommandSource(sourceName)
            }
        }
    }

    Timer {
        id: usageRefreshTimer

        interval: Math.max(1, root.refreshIntervalSec) * 1000
        repeat: true
        running: root.refreshIntervalSec > 0
        triggeredOnStart: false
        onTriggered: {
            if (!root.hasPendingPeriodicRefreshCommands()) {
                root.refreshNow(false)
            }
        }
    }

    Timer {
        id: panelClockTimer

        interval: root.panelClockIntervalMs
        repeat: true
        running: root.providers.length > 0
        triggeredOnStart: false
        onTriggered: {
            root.panelClockMs = Date.now()
            root.expireStaleUsage(root.panelClockMs)
        }
    }

    Timer {
        id: costRefreshTimer

        interval: root.costAutoRefreshIntervalMs
        repeat: true
        running: root.costCommandSource.length > 0
        triggeredOnStart: false
        onTriggered: root.refreshCost(false)
    }

    Timer {
        id: sessionsRefreshTimer

        interval: root.sessionsStaleAfterMs
        repeat: false
        running: false
        triggeredOnStart: false
        onTriggered: root.refreshSessionsIfStale()
    }

    Timer {
        id: commandTimeoutTimer

        interval: 1000
        repeat: true
        running: root.hasPendingCommandTimeouts()
        triggeredOnStart: false
        onTriggered: root.expireCommands(Date.now())
    }

    Plasma5Support.DataSource {
        id: providerConfigWatcher

        engine: "executable"
        interval: root.providerConfigWatchIntervalMs

        onNewData: function(sourceName, data) {
            if (sourceName !== root.connectedProviderConfigWatchCommand) {
                providerConfigWatcher.disconnectSource(sourceName)
                return
            }
            var stdoutText = data && data["stdout"] ? data["stdout"] : ""
            root.handleProviderConfigWatch(stdoutText)
        }
    }

    Plasma5Support.DataSource {
        id: notificationSource

        engine: "executable"

        onNewData: function(sourceName, data) {
            var descriptor = CommandLedger.find(root.activeCommandDescriptors, sourceName)
            if (!descriptor || descriptor.kind !== "notification") {
                return
            }
            root.finishNotificationCommandSource(sourceName)
        }
    }

    Controllers.WidgetUpdateController {
        updateChecksEnabled: Plasmoid.configuration.updateChecksEnabled !== false
        autoUpdateEnabled: Plasmoid.configuration.autoUpdateEnabled === true
        autoUpdateIntervalHours: isFinite(Number(Plasmoid.configuration.autoUpdateIntervalHours))
            ? Math.max(1, Math.min(168, Number(Plasmoid.configuration.autoUpdateIntervalHours))) : 24
        autoUpdateLastCheck: Plasmoid.configuration.autoUpdateLastCheck || ""

        onStatusRecorded: function(statusText, errorText) {
            Plasmoid.configuration.widgetUpdateLastStatus = statusText
            Plasmoid.configuration.widgetUpdateLastError = errorText
        }
        onCheckSucceeded: function(timestamp) {
            Plasmoid.configuration.autoUpdateLastCheck = timestamp
        }
        onUpdateAvailable: function(version, assetUrl) {
            root.notifyAvailableUpdate(version, assetUrl)
        }
        onUpdateInstalled: function(version) {
            root.notifyInstalledUpdate(version)
        }
    }

    compactRepresentation: Components.CompactRepresentation {
        applet: root
    }

    fullRepresentation: Components.FullRepresentation {
        applet: root
    }
}