#!/usr/bin/env bash
# codexbar — OpenAI Codex plan usage widget for Waybar
# Reads OAuth credentials from Codex CLI, shows session/weekly/review limits
# with colored progress bars and Pango markup tooltip.
#
# Usage: codexbar [--icon ICON] [--format FORMAT] [--tooltip-format FORMAT] [--pace-tolerance N]
#                  [--format-pace-color] [--tooltip-pace-pts] [--remaining]
#                  [--color-low HEX] [--color-mid HEX] [--color-high HEX] [--color-critical HEX]
#
# Format placeholders:
#   {plan}              Plan label (e.g. "Plus")
#   {session_pct}            Session (5h) usage %
#   {session_remaining_pct}  Session (5h) remaining %
#   {session_reset}          Session countdown (e.g. "1h 30m")
#   {session_elapsed}        Session time elapsed %
#   {session_bar}            Session usage progress bar (Pango-colored)
#   {session_remaining_bar}  Session remaining progress bar (Pango-colored)
#   {session_pace}           Session pacing icon, ratio-based (↑ ↓ →)
#   {session_pace_indicator} Session pacing icon, point-based (↑ ↓ →)
#   {session_pace_pct}       Session pacing deviation, ratio (e.g. "72% ahead")
#   {session_pace_pts}       Session pacing deviation, points (e.g. "12pts ahead")
#   {session_pace_delta}     Session pacing delta, signed (e.g. "-12", "5", "0")
#   {session_pace_abs_delta} Session pacing delta, unsigned (e.g. "12", "5", "0")
#   {weekly_pct}             Weekly usage %
#   {weekly_remaining_pct}   Weekly remaining %
#   {weekly_reset}           Weekly countdown (e.g. "4d 1h")
#   {weekly_elapsed}         Weekly time elapsed %
#   {weekly_bar}             Weekly usage progress bar (Pango-colored)
#   {weekly_remaining_bar}   Weekly remaining progress bar (Pango-colored)
#   {weekly_pace}            Weekly pacing icon, ratio-based (↑ ↓ →)
#   {weekly_pace_indicator}  Weekly pacing icon, point-based (↑ ↓ →)
#   {weekly_pace_pct}        Weekly pacing deviation, ratio (e.g. "72% ahead")
#   {weekly_pace_pts}        Weekly pacing deviation, points (e.g. "12pts ahead")
#   {weekly_pace_delta}      Weekly pacing delta, signed (e.g. "-12", "5", "0")
#   {weekly_pace_abs_delta}  Weekly pacing delta, unsigned (e.g. "12", "5", "0")
#   {review_pct}             Code review usage %
#   {review_remaining_pct}   Code review remaining %
#   {review_reset}           Code review countdown
#   {review_elapsed}         Code review time elapsed %
#   {review_bar}             Code review usage progress bar (Pango-colored)
#   {review_remaining_bar}   Code review remaining progress bar (Pango-colored)
#   {review_pace}            Review pacing icon, ratio-based (↑ ↓ →)
#   {review_pace_indicator}  Review pacing icon, point-based (↑ ↓ →)
#   {review_pace_pct}        Review pacing deviation, ratio (e.g. "72% ahead")
#   {review_pace_pts}        Review pacing deviation, points (e.g. "8pts ahead")
#   {review_pace_delta}      Review pacing delta, signed (e.g. "-8", "3", "0")
#   {review_pace_abs_delta}  Review pacing delta, unsigned (e.g. "8", "3", "0")
#   {credits_balance}   Credits balance
#   {credits_local}     Approx local messages remaining
#   {credits_cloud}     Approx cloud messages remaining
#
# Default format: "{session_pct}% · {session_reset}"
# Note: bar placeholders are colored by their own window's usage, independent of
#       the surrounding bar text color (which reflects the worst window).
# Note: --remaining            Show "what's left": default bar + tooltip flip to remaining
#                              (drain bars + remaining-time marker). User --format/--tooltip-format win.

set -euo pipefail

# --- Parse args ---
FORMAT="{session_pct}% · {session_reset}"
TOOLTIP_FORMAT=""
PACE_TOLERANCE=5
BAR_COLOR_LOW=""
BAR_COLOR_MID=""
BAR_COLOR_HIGH=""
BAR_COLOR_CRITICAL=""
BAR_ICON=""
EMIT_JSON=false
FORMAT_PACE_COLOR=false
TOOLTIP_PACE_PTS=false
REMAINING=false
FORMAT_SET=false
FRAME=false
FRAME_FONT="JetBrainsMono Nerd Font Mono"
while [[ $# -gt 0 ]]; do
    case "$1" in
        --icon)
            [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--icon requires a value","class":"critical"}'; exit 0; }
            BAR_ICON="$2"; shift 2 ;;
        --format)
            [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--format requires a value","class":"critical"}'; exit 0; }
            FORMAT="$2"; FORMAT_SET=true; shift 2 ;;
        --tooltip-format)
            [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--tooltip-format requires a value","class":"critical"}'; exit 0; }
            TOOLTIP_FORMAT="$2"; shift 2 ;;
        --remaining) REMAINING=true; shift ;;
        --config-dir)
            [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--config-dir requires a value","class":"critical"}'; exit 0; }
            CONFIG_DIR="$2"; shift 2 ;;
        --emit-json) EMIT_JSON=true; shift ;;
        --frame) FRAME=true; shift ;;
        --frame-font) [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--frame-font requires a value","class":"critical"}'; exit 0; }
                      FRAME_FONT="$2"; shift 2 ;;
        --pace-tolerance)
            [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--pace-tolerance requires a value","class":"critical"}'; exit 0; }
            PACE_TOLERANCE="$2"; shift 2 ;;
        --format-pace-color) FORMAT_PACE_COLOR=true; shift ;;
        --tooltip-pace-pts) TOOLTIP_PACE_PTS=true; shift ;;
        --color-low)       [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--color-low requires a value","class":"critical"}'; exit 0; }
                           BAR_COLOR_LOW="$2"; shift 2 ;;
        --color-mid)       [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--color-mid requires a value","class":"critical"}'; exit 0; }
                           BAR_COLOR_MID="$2"; shift 2 ;;
        --color-high)      [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--color-high requires a value","class":"critical"}'; exit 0; }
                           BAR_COLOR_HIGH="$2"; shift 2 ;;
        --color-critical)  [[ $# -ge 2 ]] || { echo '{"text":"⚠","tooltip":"--color-critical requires a value","class":"critical"}'; exit 0; }
                           BAR_COLOR_CRITICAL="$2"; shift 2 ;;
        *) _o="${1//\\/\\\\}"; _o="${_o//\"/\\\"}"; echo '{"text":"⚠","tooltip":"Unknown option: '"$_o"'","class":"critical"}'; exit 0 ;;
    esac
done
[[ "$PACE_TOLERANCE" =~ ^[0-9]+$ ]] || { echo '{"text":"⚠","tooltip":"--pace-tolerance must be a non-negative integer","class":"critical"}'; exit 0; }
PACE_TOLERANCE=$((10#$PACE_TOLERANCE))
# In remaining mode, default the bar to "what's left" unless the user set their own format.
[[ "$REMAINING" == "true" && "$FORMAT_SET" == "false" ]] && FORMAT="{session_remaining_pct}% · {session_reset}"

# Account selection mirrors claudebar: --config-dir, else CODEX_HOME, else the
# default profile; each account caches under its own slug.
CONFIG_DIR="${CONFIG_DIR:-${CODEX_HOME:-$HOME/.codex}}"
CONFIG_DIR="${CONFIG_DIR%/}"
CREDS="$CONFIG_DIR/auth.json"
API_URL="https://chatgpt.com/backend-api/wham/usage"
TOKEN_URL="https://auth.openai.com/oauth/token"
CLIENT_ID="app_EMoamEEZ73f0CkXaXp7hrann" # Codex CLI OAuth client ID
REFRESH_BUFFER=300 # refresh 5 min before expiry
CACHE_DIR="$HOME/.cache/codexbar"
if [[ "$CONFIG_DIR" != "$HOME/.codex" ]]; then
    _slug="${CONFIG_DIR#"$HOME"/}"
    _slug="${_slug//\//_}"
    _slug="${_slug//[^a-zA-Z0-9._-]/_}"
    CACHE_DIR="$CACHE_DIR/${_slug:-alt}"
fi
CACHE_TTL=60 # seconds

# Transient (no-HTTP-response) failures retry on a deadline budget, not a fixed
# attempt count. Boot-like starts (no cache, or cache older than
# NET_BOOT_CACHE_AGE) poll up to NET_LONG_BUDGET seconds: at the first Waybar
# exec there is nothing better to show than an empty module, and on re-execs
# the bar keeps the previous output while we wait — waiting beats rendering
# day-old data for a full interval. Mid-session blips keep the quick budget.
# CODEXBAR_TEST_NET_* envs let the test suite shrink the budgets; they are
# not a user interface.
_env_uint() { if [[ "$1" =~ ^[0-9]+$ ]]; then printf '%s' "$1"; else printf '%s' "$2"; fi; }
NET_RETRY_DELAY=$(_env_uint "${CODEXBAR_TEST_NET_RETRY_DELAY:-}" 2)
NET_QUICK_BUDGET=$(_env_uint "${CODEXBAR_TEST_NET_QUICK_BUDGET:-}" 6)
NET_LONG_BUDGET=$(_env_uint "${CODEXBAR_TEST_NET_LONG_BUDGET:-}" 20)
NET_BOOT_CACHE_AGE=600 # cache older than this → boot-like start
NET_WAIT_RECENT=45     # .net_wait younger than this → a sibling already waited

# --- Theme colors ---
# Blend two hex colors (#RRGGBB) by averaging their RGB components
hex_blend() {
    local c1="${1#\#}" c2="${2#\#}"
    local r1=$((16#${c1:0:2})) g1=$((16#${c1:2:2})) b1=$((16#${c1:4:2}))
    local r2=$((16#${c2:0:2})) g2=$((16#${c2:2:2})) b2=$((16#${c2:4:2}))
    printf '#%02x%02x%02x' $(( (r1+r2)/2 )) $(( (g1+g2)/2 )) $(( (b1+b2)/2 ))
}

load_theme_colors() {
    local theme_file="${XDG_CONFIG_HOME:-$HOME/.config}/vshell/theme.json"
    if [[ -f "$theme_file" ]]; then
        local accent="" foreground="" background="" color1="" color2="" color3=""
        # VGS writes the active palette here; ANSI 1/2/3 drive the bar states.
        IFS=$'\t' read -r accent foreground background color1 color2 color3 < <(
            jq -r '.colors | [.accent, .foreground, .background, .red, .green, .yellow]
                   | map(. // "") | @tsv' "$theme_file" 2>/dev/null
        )
        # Map theme colors to widget colors
        [[ -n "$accent" ]]     && BLUE="$accent"
        [[ -n "$foreground" ]] && FG="$foreground"
        [[ -n "$color1" ]]     && { RED="$color1"; ORANGE="$color1"; }
        [[ -n "$color2" ]]     && GREEN="$color2"
        [[ -n "$color3" ]]     && YELLOW="$color3"
        # DIM = midpoint between foreground and background
        if [[ -n "$foreground" && -n "$background" ]]; then
            DIM=$(hex_blend "$foreground" "$background")
        fi
        MARKER="$FG"
        # BAR_EMPTY = slightly lighter than background (blend bg with dim)
        if [[ -n "$background" ]]; then
            BAR_EMPTY=$(hex_blend "$background" "$DIM")
        fi
    fi
}

# One Dark defaults (overridden by the active VGS theme if available)
GREEN="#98c379"
YELLOW="#e5c07b"
ORANGE="#d19a66"
RED="#e06c75"
BLUE="#61afef"
DIM="#5c6370"
FG="#abb2bf"
BAR_EMPTY="#3e4451"
MARKER="$FG"
load_theme_colors

# Bar text colors: CLI overrides > theme > One Dark defaults
[[ -n "$BAR_COLOR_LOW" ]]      || BAR_COLOR_LOW="$GREEN"
[[ -n "$BAR_COLOR_MID" ]]      || BAR_COLOR_MID="$YELLOW"
[[ -n "$BAR_COLOR_HIGH" ]]     || BAR_COLOR_HIGH="$ORANGE"
[[ -n "$BAR_COLOR_CRITICAL" ]] || BAR_COLOR_CRITICAL="$RED"

BAR_LEN=20

# Window durations in seconds
SESSION_WINDOW=$(( 5 * 3600 ))      # 5 hours
WEEKLY_WINDOW=$(( 7 * 24 * 3600 ))  # 7 days

# --- Helpers ---

die() {
    # Widget-bridge mode: one account failing is not the whole bar failing.
    if [[ "${EMIT_JSON:-false}" == "true" ]] && command -v jq &>/dev/null; then
        jq -nc --arg err "$1" --arg config_dir "${CONFIG_DIR:-}" \
            '{ok:false, error:$err, configDir:$config_dir}'
        exit 0
    fi
    if command -v jq &>/dev/null; then
        jq -nc --arg t "⚠" --arg tip "$1" '{text:$t, tooltip:$tip, class:"critical"}'
    else
        local _m="${1//\\/\\\\}"; _m="${_m//\"/\\\"}"
        printf '{"text":"⚠","tooltip":"%s","class":"critical"}\n' "$_m"
    fi
    exit 0
}

# Soft exit for transient network failures with no usable cache. Emits a neutral
# "Loading…" widget in the low class so Waybar styles it like normal usage and
# doesn't draw attention to a state that will self-resolve on the next dispatch.
loading_network() {
    jq -nc \
        --arg t "${BAR_ICON:+${BAR_ICON} }Loading…" \
        --arg tip "Waiting for network — no cached usage yet.\nWill retry shortly." \
        '{text:$t, tooltip:$tip, class:"low"}'
    exit 0
}

# Relative age for stale messaging. Negative (clock skew) → empty, caller omits it.
rel_age() {
    local s=$1
    if   (( s < 0 ));     then return 0
    elif (( s < 60 ));    then printf 'just now'
    elif (( s < 3600 ));  then printf '%d min ago' $(( s / 60 ))
    elif (( s < 86400 )); then printf '%d h ago' $(( s / 3600 ))
    else printf '%d d ago' $(( s / 86400 ))
    fi
}

color_for() {
    local p=$1
    if   (( p >= 90 )); then printf '%s' "$RED"
    elif (( p >= 75 )); then printf '%s' "$ORANGE"
    elif (( p >= 50 )); then printf '%s' "$YELLOW"
    else printf '%s' "$GREEN"
    fi
}

pango_escape() {
    sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' <<< "${1:-}"
}

format_plan_label() {
    local raw="${1:-unknown}" label
    case "$raw" in
        prolite|pro_lite|pro-lite) printf 'Pro Lite' ;;
        self_serve_business_usage_based) printf 'Business Usage Based' ;;
        enterprise_cbp_usage_based) printf 'Enterprise Usage Based' ;;
        *)
            label="${raw//[_-]/ }"
            printf '%s' "${label^}"
            ;;
    esac
}

is_approx_window() {
    local seconds=$1 expected=$2
    (( seconds >= expected * 95 / 100 && seconds <= expected * 105 / 100 ))
}

window_label_for_seconds() {
    local seconds=${1:-0} is_secondary=${2:-0}
    local hour=$(( 60 * 60 ))
    local day=$(( 24 * hour ))
    if is_approx_window "$seconds" "$(( 5 * hour ))"; then
        printf '5h'
    elif is_approx_window "$seconds" "$day"; then
        printf 'daily'
    elif is_approx_window "$seconds" "$(( 7 * day ))"; then
        printf 'weekly'
    elif is_approx_window "$seconds" "$(( 30 * day ))"; then
        printf 'monthly'
    elif is_approx_window "$seconds" "$(( 365 * day ))"; then
        printf 'annual'
    elif (( is_secondary )); then
        printf 'secondary usage'
    else
        printf 'usage'
    fi
}

remaining_pct_for() {
    local p=${1:-0}
    (( p < 0 )) && p=0
    (( p > 100 )) && p=100
    printf '%s' "$(( 100 - p ))"
}

# Percentage to DISPLAY for a window under the current framing.
display_pct() {  # <used_pct>
    if [[ "$REMAINING" == "true" ]]; then remaining_pct_for "$1"; else printf '%s' "$1"; fi
}

# Progress bar to DISPLAY for a window under the current framing.
# Usage: display_bar <used_pct> <elapsed_pct> [with_marker]
# Color is always severity (color_for used_pct), in both framings.
display_bar() {
    local used=$1 elapsed=$2 with_marker=${3:-} color
    color=$(color_for "$used")
    if [[ "$REMAINING" == "true" ]]; then
        local rem; rem=$(remaining_pct_for "$used")
        if [[ -n "$with_marker" ]]; then
            make_bar "$rem" "$color" "$(( 100 - elapsed ))" "$MARKER"
        else
            make_bar "$rem" "$color"
        fi
    else
        if [[ -n "$with_marker" ]]; then
            make_bar "$used" "$color" "$elapsed" "$MARKER"
        else
            make_bar "$used" "$color"
        fi
    fi
}

# Color based on signed pacing delta, centered around zero:
#   <= -10  green   (well under pace, plenty of room)
#   -10..0  yellow  (slightly under or exactly on pace)
#   1..+9   orange  (slightly ahead, watch it)
#   >= +10  red     (burning fast)
pace_color_for() {
    local delta=${1:-0}
    if   (( delta >= 10 ));  then printf '%s' "$RED"
    elif (( delta > 0 ));    then printf '%s' "$ORANGE"
    elif (( delta >= -10 )); then printf '%s' "$YELLOW"
    else printf '%s' "$GREEN"
    fi
}

# Build a progress bar with optional elapsed marker.
# Usage: make_bar <pct> <color> [marker_pct] [marker_color]
make_bar() {
    local p=$1 color=$2 marker_pos=${3:--1} marker_color=${4:-}
    local filled=$(( p * BAR_LEN / 100 ))
    (( filled > BAR_LEN )) && filled=$BAR_LEN
    (( filled < 0 )) && filled=0

    local f="" e=""
    if (( marker_pos < 0 )); then
        printf -v f '%*s' "$filled" ''; printf -v e '%*s' "$(( BAR_LEN - filled ))" ''
        printf "<span foreground='%s'>%s</span><span foreground='%s'>%s</span>" \
            "$color" "${f// /█}" "$BAR_EMPTY" "${e// /░}"
        return
    fi

    local m=$(( marker_pos * BAR_LEN / 100 ))
    (( m > BAR_LEN - 1 )) && m=$(( BAR_LEN - 1 ))
    local pre_f=$(( filled < m ? filled : m ))
    local post_f=$(( filled > m + 1 ? filled - m - 1 : 0 ))

    printf -v f '%*s' "$pre_f" ''; printf -v e '%*s' "$(( m - pre_f ))" ''
    printf "<span foreground='%s'>%s</span><span foreground='%s'>%s</span>" \
        "$color" "${f// /█}" "$BAR_EMPTY" "${e// /░}"
    printf "<span foreground='%s'>┃</span>" "$marker_color"
    printf -v f '%*s' "$post_f" ''; printf -v e '%*s' "$(( BAR_LEN - m - 1 - post_f ))" ''
    printf "<span foreground='%s'>%s</span><span foreground='%s'>%s</span>" \
        "$color" "${f// /█}" "$BAR_EMPTY" "${e// /░}"
}

# Convert unix timestamp to countdown string
countdown() {
    local ts=$1
    [[ -z "$ts" || "$ts" == "0" || "$ts" == "null" ]] && { echo "—"; return; }
    local now diff d h m
    now=$(date +%s)
    diff=$(( ts - now ))
    (( diff <= 0 )) && { echo "now"; return; }
    d=$(( diff / 86400 ))
    h=$(( diff % 86400 / 3600 ))
    m=$(( diff % 3600 / 60 ))
    if (( d > 0 )); then
        echo "${d}d ${h}h"
    else
        printf '%dh %02dm' "$h" "$m"
    fi
}

# Calculate time elapsed % and pacing for a usage window.
# Uses point-based delta (actual - expected) for the icon and delta values.
# Also computes the original ratio-based label for backward-compatible placeholders.
#
# Args: usage_pct reset_at_unix window_duration_seconds
# Output fields (6 logical, 8 space-separated tokens due to multi-word labels):
#   elapsed_pct pace_icon pace_indicator pace_delta pace_pct_label pace_pts_label
calc_pacing() {
    local usage_pct=$1 reset_at=$2 window_s=$3
    local elapsed_pct=0 pace_icon="→" pace_indicator="→" pace_delta="0"
    local pace_pct_label="on track" pace_pts_label="on track"

    if [[ -n "$reset_at" && "$reset_at" != "0" && "$reset_at" != "null" ]] && (( window_s > 0 )); then
        local now_epoch secs_left
        now_epoch=$(date +%s)
        secs_left=$(( reset_at - now_epoch ))
        elapsed_pct=$(( (window_s - secs_left) * 100 / window_s ))
        (( elapsed_pct < 0 )) && elapsed_pct=0
        (( elapsed_pct > 100 )) && elapsed_pct=100

        # Point-based delta for indicator, delta, and pts label
        local delta=$(( usage_pct - elapsed_pct ))
        pace_delta="$delta"
        if (( delta > 0 )); then
            pace_indicator="↑"
            pace_pts_label="${delta}pts ahead"
        elif (( delta < 0 )); then
            pace_indicator="↓"
            pace_pts_label="$(( -delta ))pts under"
        fi

        # Ratio-based icon and label for backward-compatible {*_pace} and {*_pace_pct}
        if (( elapsed_pct > 0 )); then
            local pacing_x100=$(( usage_pct * 100 / elapsed_pct ))
            if (( pacing_x100 > 100 + PACE_TOLERANCE )); then
                local dev=$(( pacing_x100 - 100 ))
                (( dev > 999 )) && dev=999
                pace_icon="↑"
                pace_pct_label="${dev}% ahead"
            elif (( pacing_x100 < 100 - PACE_TOLERANCE )); then
                local dev=$(( 100 - pacing_x100 ))
                (( dev > 999 )) && dev=999
                pace_icon="↓"
                pace_pct_label="${dev}% under"
            fi
        fi
    fi

    echo "${elapsed_pct} ${pace_icon} ${pace_indicator} ${pace_delta} ${pace_pct_label} ${pace_pts_label}"
}

# Decode JWT payload (base64url → JSON)
jwt_decode() {
    local token=$1 payload pad decoded
    payload=$(echo "$token" | cut -d. -f2)
    pad=$(( 4 - ${#payload} % 4 ))
    (( pad < 4 )) && payload+=$(printf '%*s' "$pad" '' | tr ' ' '=')
    payload="${payload//-/+}"
    payload="${payload//_//}"
    decoded=$(echo "$payload" | base64 -d 2>/dev/null || true)
    # Always emit exactly one JSON OBJECT ("{}" for any malformed, empty, or
    # non-object payload) so callers piping into jq never hit a parse OR index error.
    printf '%s' "$decoded" | jq -c -s 'if length == 1 and (.[0] | type == "object") then .[0] else {} end' 2>/dev/null || printf '{}'
}

jwt_exp() { jwt_decode "$1" | jq -r '((.exp | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end'; }

# --- Pre-checks ---
for cmd in curl jq date base64; do
    command -v "$cmd" &>/dev/null || die "Missing dependency: $cmd"
done
[[ -f "$CREDS" ]] || die "No credentials.\nRun <b>codex login</b> to log in."

# --- Cache / lock setup ---
mkdir -p "$CACHE_DIR"
cache_file="$CACHE_DIR/usage.json"
stale_marker="$CACHE_DIR/.stale"
net_wait_marker="$CACHE_DIR/.net_wait"
_lockfile="$CACHE_DIR/.fetch.lock"

_fetch_usage() {
    if [[ -f "$cache_file" ]] && (( cache_age < CACHE_TTL )); then
        usage=$(<"$cache_file")
        return
    fi

    if [[ "$auth_ok" == "false" ]]; then
        if [[ "$refresh_failure_kind" == "transient" ]]; then
            # Network blip — stay quiet. Reuse recent cache with an in-memory
            # stale flag, or show neutral "Loading…" if no usable cache exists.
            # No disk markers, no desktop notification.
            if [[ -f "$cache_file" ]] && (( cache_age <= WEEKLY_WINDOW )); then
                usage=$(<"$cache_file")
                transient_stale=true
                return
            fi
            loading_network
        fi
        # Hard refresh failure — preserve actionable signal (.last_error was
        # written by the refresh path so the tooltip can explain the failure).
        if [[ -f "$cache_file" ]] && (( cache_age <= WEEKLY_WINDOW )); then
            usage=$(<"$cache_file")
            if [[ ! -f "$stale_marker" ]]; then
                touch "$stale_marker"
                command -v notify-send &>/dev/null && \
                    notify-send -u normal -i dialog-warning \
                        "Codex Usage" "Token refresh failed. Check connection or run <b>codex login</b>." 2>/dev/null || true
            fi
        else
            die "Token refresh failed.\nCheck connection or run <b>codex login</b>."
        fi
        return
    fi

    # Transient failures (no HTTP response: DHCP/DNS still settling at boot,
    # brief blip) are retried until the shared deadline — one missed dispatch
    # otherwise means stale data for a full Waybar interval.
    # Bearer token via stdin config, never argv — /proc/<pid>/cmdline is world-readable.
    _fetch_args=(-s --max-time 10 -w $'\n%{http_code}' "$API_URL")
    [[ -n "$account_id" ]] && _fetch_args+=(-H "chatgpt-account-id: $account_id")
    while :; do
        _resp_body=$(curl "${_fetch_args[@]}" -K - <<CURLCFG
header = "Authorization: Bearer ${access_token}"
CURLCFG
) || true
        _http_code="${_resp_body##*$'\n'}"
        _resp_body="${_resp_body%$'\n'"$_http_code"}"
        # Normalize bogus codes (e.g. raw body without curl write-out suffix) to 000.
        [[ "$_http_code" =~ ^[0-9]{3}$ ]] || _http_code="000"
        if [[ "$_http_code" != "000" ]]; then break; fi
        if (( SECONDS >= net_deadline )); then break; fi
        sleep "$NET_RETRY_DELAY"
    done

    if [[ "$_http_code" == "200" ]] && [[ -n "$_resp_body" ]] && jq -e 'type == "object" and (has("rate_limit") or has("additional_rate_limits") or has("credits"))' <<< "$_resp_body" &>/dev/null; then
        usage="$_resp_body"
        _ctmp=$(mktemp "$CACHE_DIR/.usage.XXXXXX")
        printf '%s' "$usage" > "$_ctmp" && mv "$_ctmp" "$cache_file"
        rm -f "$stale_marker" "$net_wait_marker" "$CACHE_DIR/.last_error"
    elif [[ "$_http_code" == "000" ]]; then
        # No HTTP response after the retry budget — network still settling or
        # offline. Mark the episode so sibling instances skip their own long
        # wait, then reuse recent cache with an in-memory stale flag (no .stale
        # on disk), or show neutral "Loading…".
        if [[ "$net_long" == "true" ]]; then touch "$net_wait_marker" 2>/dev/null || true; fi
        if [[ -f "$cache_file" ]] && (( cache_age <= WEEKLY_WINDOW )); then
            usage=$(<"$cache_file")
            transient_stale=true
        else
            loading_network
        fi
    elif [[ -f "$cache_file" ]]; then
        usage=$(<"$cache_file")
        touch "$stale_marker"
        if [[ "$_http_code" =~ ^[45][0-9]{2}$ ]]; then
            _err_msg=$(jq -r '.error.message // empty' <<< "$_resp_body" 2>/dev/null) || true
            printf '%s\n%s' "$_http_code" "$_err_msg" > "$CACHE_DIR/.last_error"
        fi
    else
        die "API request failed.\nCheck your connection."
    fi
}

# --- Acquire lock for the state-changing window (credential read + refresh + fetch) ---
# Multi-monitor: peer instances may rotate the refresh_token between a pre-lock
# read and our refresh attempt (the provider rotates refresh tokens); reading
# auth.json only under the lock prevents a stale-token refresh from hard-failing
# with a spurious "run codex login" while credentials are actually fine.
exec 9>"$_lockfile"
# Generous wait: a boot-like sibling may hold the lock for the long budget plus
# in-flight curls (worst ≈ NET_LONG_BUDGET + 25s refresh + 10s fetch ≈ 57s);
# rendering late beats dying with a visible "Cache lock timeout".
flock -w 90 9 || die "Cache lock timeout"

# --- Transient retry budget (shared by token refresh and usage fetch) ---
now_s=$(date +%s)
cache_age=-1
[[ -f "$cache_file" ]] && cache_age=$(( now_s - $(date -r "$cache_file" +%s) ))
# Missing cache or future mtime (clock skew) → boot-like, never "fresh".
if (( cache_age < 0 )); then cache_age=$(( NET_BOOT_CACHE_AGE + 1 )); fi
net_budget=$NET_QUICK_BUDGET
net_long=false
if (( cache_age > NET_BOOT_CACHE_AGE )); then
    net_budget=$NET_LONG_BUDGET
    net_long=true
    if [[ -f "$net_wait_marker" ]]; then
        _nw_age=$(( now_s - $(date -r "$net_wait_marker" +%s) ))
        # A sibling already burned the long budget this episode — single
        # attempt, no sleeps. Future mtimes (clock skew) don't count.
        if (( _nw_age >= 0 && _nw_age <= NET_WAIT_RECENT )); then
            net_budget=0
            net_long=false
        fi
    fi
fi
net_deadline=$(( SECONDS + net_budget ))

# --- Read credentials (under the lock) ---
[[ -f "$CREDS" ]] || die "No credentials.\nRun <b>codex login</b> to log in."
creds=$(<"$CREDS")
jq -e . <<< "$creds" &>/dev/null || die "Invalid credentials file.\nRun <b>codex login</b> to log in."
access_token=$(jq -r '.tokens.access_token // empty' <<< "$creds")
refresh_token=$(jq -r '.tokens.refresh_token // empty' <<< "$creds")
account_id=$(jq -r '.tokens.account_id // empty' <<< "$creds")

[[ -n "$access_token" ]] || die "No token.\nRun <b>codex login</b> to log in."

# Extract plan type from id_token JWT claims
id_token_raw=$(jq -r '.tokens.id_token // empty' <<< "$creds")
plan_type="unknown"
if [[ -n "$id_token_raw" ]]; then
    _pt=$(jwt_decode "$id_token_raw" | jq -r '(."https://api.openai.com/auth".chatgpt_plan_type? // empty)')
    [[ -n "$_pt" ]] && plan_type="$_pt"
fi

# --- Token refresh if needed (under the lock) ---
expires_s=$(jwt_exp "$access_token")
auth_ok=true
refresh_failure_kind=""
transient_stale=false

if (( expires_s < now_s + REFRESH_BUFFER )); then
    # Synchronous refresh — 25s timeout covers cold boot when DHCP is still settling.
    # Transient failures (no HTTP response) are retried until the shared deadline —
    # same policy as the usage fetch — so a cold-boot race doesn't cost a full interval.
    refresh_body=$(jq -nc --arg ci "$CLIENT_ID" --arg rt "$refresh_token" \
                     '{client_id:$ci, grant_type:"refresh_token", refresh_token:$rt, scope:"openid profile email"}')
    while :; do
        refresh_raw=$(curl -s --max-time 25 -w '\n%{http_code}' -X POST "$TOKEN_URL" \
            -H "Content-Type: application/json" \
            --data @- <<<"$refresh_body") || true
        refresh_code="${refresh_raw##*$'\n'}"
        resp="${refresh_raw%$'\n'"$refresh_code"}"
        # Normalize bogus codes (e.g. raw body without curl write-out suffix) to 000.
        [[ "$refresh_code" =~ ^[0-9]{3}$ ]] || refresh_code="000"
        if [[ "$refresh_code" != "000" ]]; then break; fi
        if (( SECONDS >= net_deadline )); then break; fi
        sleep "$NET_RETRY_DELAY"
    done

    if [[ "$refresh_code" == "200" ]] && [[ -n "$resp" ]] && jq -e '.access_token' <<< "$resp" &>/dev/null; then
        new_at=$(jq -r '.access_token // empty' <<< "$resp")
        new_rt=$(jq -r '.refresh_token // empty' <<< "$resp")
        new_idt=$(jq -r '.id_token // empty' <<< "$resp")
        now_iso=$(date -u +%Y-%m-%dT%H:%M:%S.000000000Z)

        [[ -n "$new_at" ]] && access_token="$new_at"

        tmp=$(mktemp)
        jq --arg at "${new_at:-$access_token}" \
           --arg rt "${new_rt:-$refresh_token}" \
           --arg idt "${new_idt:-$id_token_raw}" \
           --arg lr "$now_iso" \
           '.tokens.access_token=$at | .tokens.refresh_token=$rt | .tokens.id_token=$idt | .last_refresh=$lr' \
           "$CREDS" > "$tmp" && mv "$tmp" "$CREDS"
        rm -f "$CACHE_DIR/.last_error"
    else
        auth_ok=false
        if [[ "$refresh_code" == "000" ]]; then
            # No HTTP response reached us — DHCP/DNS settling at boot, offline,
            # or actual timeout. Mark the episode so sibling instances skip
            # their own long wait; nothing else persists.
            refresh_failure_kind="transient"
            if [[ "$net_long" == "true" ]]; then touch "$net_wait_marker" 2>/dev/null || true; fi
        else
            refresh_failure_kind="hard"
            # Persist the failure so the tooltip can say WHY data is stale.
            # platform returns OAuth-style {error,error_description}; fall back
            # to Anthropic-style {error:{message}} or bare {error:"<str>"}.
            refresh_err=""
            if [[ "$refresh_code" =~ ^[45][0-9]{2}$ ]]; then
                refresh_err=$(jq -r '
                    if (.error_description | type) == "string" then .error_description
                    elif (.error | type) == "object" and (.error | has("message")) and (.error.message | type) == "string" then .error.message
                    elif (.error | type) == "string" then .error
                    else "" end' <<< "$resp" 2>/dev/null) || true
                [[ -z "$refresh_err" ]] && refresh_err="Refresh failed"
            else
                refresh_err="Invalid refresh response"
            fi
            # Best-effort write — never abort the widget if the cache dir is unwriteable.
            _le_tmp=$(mktemp "$CACHE_DIR/.last_error.XXXXXX" 2>/dev/null) || _le_tmp=""
            if [[ -n "$_le_tmp" ]]; then
                if printf '%s\n%s' "$refresh_code" "$refresh_err" > "$_le_tmp" 2>/dev/null \
                   && mv "$_le_tmp" "$CACHE_DIR/.last_error" 2>/dev/null; then
                    :
                else
                    rm -f "$_le_tmp" 2>/dev/null || true
                fi
            fi
        fi
    fi
fi

_fetch_usage
exec 9>&-

# A corrupt/partial/multi-document cache could make the per-field reads emit
# garbage or parse-error; require exactly one valid JSON document up front.
jq -e -s 'length == 1' <<< "$usage" &>/dev/null || die "Invalid usage data.\nWill refresh next cycle."

# --- Parse windows ---
# OpenAI reports rate limits as primary/secondary windows whose DURATIONS vary
# by account and shift over time: a weekly-only account reports its 7-day limit
# as the *primary* window with a null secondary. Routing primary→session /
# secondary→weekly blindly then mislabels a 7-day window as "5h" and paints a
# phantom 0% weekly. Instead, read each window generically and route it to the
# session (≈5h) or weekly (≥ a day) slot by its own limit_window_seconds.
_win_field() { # <window> <field> <default>
    jq -r "try (((.rate_limit.${1}.${2} | numbers) // ${3}) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch ${3}" <<< "$usage"
}
_win_present() { # <window>
    jq -r "try (if .rate_limit.${1} != null then \"true\" else \"false\" end) catch \"false\"" <<< "$usage"
}

session_pct=0; session_reset_at=0; session_window_s=$SESSION_WINDOW; has_session=false
weekly_pct=0;  weekly_reset_at=0;  weekly_window_s=$WEEKLY_WINDOW;  has_weekly=false

route_window() { # <window-name>
    [[ "$(_win_present "$1")" == "true" ]] || return 0
    local pct reset win
    pct=$(_win_field "$1" used_percent 0)
    reset=$(_win_field "$1" reset_at 0)
    win=$(_win_field "$1" limit_window_seconds 0)
    (( win > 0 )) || win=$SESSION_WINDOW
    if is_approx_window "$win" "$SESSION_WINDOW"; then
        session_pct=$pct; session_reset_at=$reset; session_window_s=$win; has_session=true
    else
        # Anything materially longer than the 5h session (daily/weekly/monthly)
        # fills the weekly slot; the widget labels it "Weekly".
        weekly_pct=$pct; weekly_reset_at=$reset; weekly_window_s=$win; has_weekly=true
    fi
}
route_window primary_window
route_window secondary_window
session_remaining_pct=$(remaining_pct_for "$session_pct")
weekly_remaining_pct=$(remaining_pct_for "$weekly_pct")

# Code review
has_review=$(jq 'try (.code_review_rate_limit != null and .code_review_rate_limit.primary_window != null) catch false' <<< "$usage")
review_pct=0
review_reset_at=0
if [[ "$has_review" == "true" ]]; then
    review_pct=$(jq -r 'try (((.code_review_rate_limit.primary_window.used_percent | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$usage")
    review_reset_at=$(jq -r 'try (((.code_review_rate_limit.primary_window.reset_at | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$usage")
    review_window_s=$(jq -r 'try (((.code_review_rate_limit.primary_window.limit_window_seconds | numbers) // 604800) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 604800' <<< "$usage")
    (( review_window_s > 0 )) || review_window_s=$WEEKLY_WINDOW
fi
review_remaining_pct=$(remaining_pct_for "$review_pct")

# Generalized "third" meter for the compact bridge/widget. OpenAI dropped the
# dedicated code-review bucket for most ChatGPT/OAuth accounts (code_review_rate_limit
# is now null) and reports model-specific limits under additional_rate_limits[]
# instead (e.g. "GPT-5.3-Codex-Spark"). Prefer the legacy review bucket when
# present; otherwise surface the most-consumed additional lane, with a dynamic
# label. Only display lanes whose window is actually reported (window seconds > 0).
third_label=""; third_pct=0; third_reset_at=0; has_third=false
if [[ "$has_review" == "true" ]]; then
    third_label="Review"; third_pct=$review_pct; third_reset_at=$review_reset_at; has_third=true
else
    _third=$(jq -c 'try (
        [.additional_rate_limits[]?
          | select(type == "object")
          | select(((.rate_limit.primary_window.limit_window_seconds | numbers) // 0) > 0)]
        | max_by((.rate_limit.primary_window.used_percent | numbers) // 0) // null
    ) catch null' <<< "$usage")
    if [[ -n "$_third" && "$_third" != "null" ]]; then
        third_label=$(jq -r 'try ((.limit_name | strings | select(. != "")) // (.metered_feature | strings | select(. != "")) // "Extra") catch "Extra"' <<< "$_third")
        third_pct=$(jq -r 'try (((.rate_limit.primary_window.used_percent | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$_third")
        third_reset_at=$(jq -r 'try (((.rate_limit.primary_window.reset_at | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$_third")
        has_third=true
    fi
fi

# Additional Codex model-specific meters. Codex itself maps these as separate
# rate-limit snapshots; keep them available for tooltip and severity decisions.
additional_limits_tsv=$(jq -r '
  .additional_rate_limits[]?
  | select(type == "object")
  | [
      ((.limit_name | strings | select(. != "")) // (.metered_feature | strings | select(. != "")) // "Additional"),
      (((((try .rate_limit.primary_window.used_percent catch null) | numbers) // 0) | floor) | if (. > 1e12 or . < 0) then 0 else . end),
      (((((try .rate_limit.primary_window.reset_at catch null) | numbers) // 0) | floor) | if (. > 1e12 or . < 0) then 0 else . end),
      (((((try .rate_limit.primary_window.limit_window_seconds catch null) | numbers) // 0) | floor) | if (. > 1e12 or . < 0) then 0 else . end),
      (((((try .rate_limit.secondary_window.used_percent catch null) | numbers) // 0) | floor) | if (. > 1e12 or . < 0) then 0 else . end),
      (((((try .rate_limit.secondary_window.reset_at catch null) | numbers) // 0) | floor) | if (. > 1e12 or . < 0) then 0 else . end),
      (((((try .rate_limit.secondary_window.limit_window_seconds catch null) | numbers) // 0) | floor) | if (. > 1e12 or . < 0) then 0 else . end)
    ]
  | @tsv
' <<< "$usage")
# Severity must track what the tooltip actually renders: only count a window's
# usage when that window is displayable (limit_window_seconds > 0). Otherwise an
# additional meter could push the widget to a worse class with nothing shown.
additional_max_pct=$(jq -r '
  ([.additional_rate_limits[]?
    | select(type == "object")
    | (if (((((try .rate_limit.primary_window.limit_window_seconds catch null) | numbers) // 0)) | floor | if (. > 1e12 or . < 0) then 0 else . end) > 0
         then ((((try .rate_limit.primary_window.used_percent catch null) | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) else empty end),
      (if (((((try .rate_limit.secondary_window.limit_window_seconds catch null) | numbers) // 0)) | floor | if (. > 1e12 or . < 0) then 0 else . end) > 0
         then ((((try .rate_limit.secondary_window.used_percent catch null) | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) else empty end)
  ] | max // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end
' <<< "$usage")

# Credits
credits_balance=$(jq -r 'try (.credits.balance // "0") catch "0"' <<< "$usage")
has_credits=$(jq -r 'try (.credits.has_credits // false) catch false' <<< "$usage")
credits_local_lo=$(jq -r 'try (((.credits.approx_local_messages[0] | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$usage")
credits_local_hi=$(jq -r 'try (((.credits.approx_local_messages[1] | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$usage")
credits_cloud_lo=$(jq -r 'try (((.credits.approx_cloud_messages[0] | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$usage")
credits_cloud_hi=$(jq -r 'try (((.credits.approx_cloud_messages[1] | numbers) // 0) | floor | if (. > 1e12 or . < 0) then 0 else . end) catch 0' <<< "$usage")

# Format credits local/cloud as range strings
if (( credits_local_lo == credits_local_hi )); then
    credits_local="${credits_local_lo}"
else
    credits_local="${credits_local_lo}–${credits_local_hi}"
fi
if (( credits_cloud_lo == credits_cloud_hi )); then
    credits_cloud="${credits_cloud_lo}"
else
    credits_cloud="${credits_cloud_lo}–${credits_cloud_hi}"
fi

# --- Plan label ---
plan=$(format_plan_label "$plan_type")
# Also check API response for plan_type
_api_plan=$(jq -r '.plan_type // empty' <<< "$usage" 2>/dev/null) || true
[[ -n "$_api_plan" ]] && plan=$(format_plan_label "$_api_plan")

# --- Pacing ---
# Output: elapsed icon indicator delta pct_label pts_label
_s_pace=$(calc_pacing "$session_pct" "$session_reset_at" "$session_window_s")
read -r session_elapsed session_pace session_pace_indicator session_pace_delta _discard <<< "$_s_pace"
session_pace_pct=$(cut -d' ' -f5-6 <<< "$_s_pace")
session_pace_pts=$(cut -d' ' -f7-8 <<< "$_s_pace")

_w_pace=$(calc_pacing "$weekly_pct" "$weekly_reset_at" "$weekly_window_s")
read -r weekly_elapsed weekly_pace weekly_pace_indicator weekly_pace_delta _discard <<< "$_w_pace"
weekly_pace_pct=$(cut -d' ' -f5-6 <<< "$_w_pace")
weekly_pace_pts=$(cut -d' ' -f7-8 <<< "$_w_pace")

review_elapsed=0 review_pace="" review_pace_indicator="" review_pace_pct="" review_pace_pts="" review_pace_delta="0"
if [[ "$has_review" == "true" ]]; then
    _r_pace=$(calc_pacing "$review_pct" "$review_reset_at" "$review_window_s")
    read -r review_elapsed review_pace review_pace_indicator review_pace_delta _discard <<< "$_r_pace"
    review_pace_pct=$(cut -d' ' -f5-6 <<< "$_r_pace")
    review_pace_pts=$(cut -d' ' -f7-8 <<< "$_r_pace")
fi

# --- Tooltip pace icon (ratio or point-based) ---
if [[ "$TOOLTIP_PACE_PTS" == "true" ]]; then
    _tip_session_pace="$session_pace_indicator"
    _tip_weekly_pace="$weekly_pace_indicator"
    _tip_review_pace="$review_pace_indicator"
else
    _tip_session_pace="$session_pace"
    _tip_weekly_pace="$weekly_pace"
    _tip_review_pace="$review_pace"
fi

# --- Pace colors ---
# Sets _sp_<prefix>_{pace,indicator,pct,pts,delta,abs_delta} for apply_format.
_precompute_pace() {
    local pfx=$1 pace=$2 indicator=$3 pct=$4 pts=$5 delta=$6
    if [[ "$FORMAT_PACE_COLOR" == "true" ]]; then
        local c; c=$(pace_color_for "${delta:-0}")
        printf -v "_sp_${pfx}_pace"      "<span foreground='%s'>%s</span>" "$c" "$pace"
        printf -v "_sp_${pfx}_indicator" "<span foreground='%s'>%s</span>" "$c" "$indicator"
        printf -v "_sp_${pfx}_pct"       "<span foreground='%s'>%s</span>" "$c" "$pct"
        printf -v "_sp_${pfx}_pts"       "<span foreground='%s'>%s</span>" "$c" "$pts"
        printf -v "_sp_${pfx}_delta"     "<span foreground='%s'>%s</span>" "$c" "$delta"
        printf -v "_sp_${pfx}_abs_delta" "<span foreground='%s'>%s</span>" "$c" "${delta#-}"
    else
        printf -v "_sp_${pfx}_pace"      '%s' "$pace"
        printf -v "_sp_${pfx}_indicator" '%s' "$indicator"
        printf -v "_sp_${pfx}_pct"       '%s' "$pct"
        printf -v "_sp_${pfx}_pts"       '%s' "$pts"
        printf -v "_sp_${pfx}_delta"     '%s' "$delta"
        printf -v "_sp_${pfx}_abs_delta" '%s' "${delta#-}"
    fi
}
_precompute_pace session "$session_pace" "$session_pace_indicator" "$session_pace_pct" "$session_pace_pts" "$session_pace_delta"
_precompute_pace weekly  "$weekly_pace"  "$weekly_pace_indicator"  "$weekly_pace_pct"  "$weekly_pace_pts"  "$weekly_pace_delta"
_precompute_pace review  "$review_pace"  "$review_pace_indicator"  "$review_pace_pct"  "$review_pace_pts"  "$review_pace_delta"

# --- Waybar class ---
max_pct=$session_pct
(( weekly_pct > max_pct )) && max_pct=$weekly_pct
(( review_pct > max_pct )) && max_pct=$review_pct
(( additional_max_pct > max_pct )) && max_pct=$additional_max_pct

class=""
if   (( max_pct >= 90 )); then class="critical"
elif (( max_pct >= 75 )); then class="high"
elif (( max_pct >= 50 )); then class="mid"
else class="low"
fi

# --- Apply format placeholders ---
# Compute countdowns once
s_cd_val=$(countdown "$session_reset_at")
w_cd_val=$(countdown "$weekly_reset_at")
r_cd_val=""
[[ "$has_review" == "true" ]] && r_cd_val=$(countdown "$review_reset_at")
t_cd_val=""
[[ "$has_third" == "true" ]] && t_cd_val=$(countdown "$third_reset_at")

# Per-window colors and bars used by --format / --tooltip-format placeholders.
# Each bar is colored by its own window's usage, independent of the bar text wrapper.
session_color=$(color_for "$session_pct")
weekly_color=$(color_for "$weekly_pct")
review_color=$(color_for "$review_pct")
format_session_bar=$(make_bar "$session_pct" "$session_color")
format_weekly_bar=$(make_bar "$weekly_pct" "$weekly_color")
format_review_bar=$(make_bar "$review_pct" "$review_color")
format_session_remaining_bar=$(make_bar "$session_remaining_pct" "$session_color")
format_weekly_remaining_bar=$(make_bar "$weekly_remaining_pct" "$weekly_color")
format_review_remaining_bar=$(make_bar "$review_remaining_pct" "$review_color")

apply_format() {
    local text="$1"
    text="${text//\{icon\}/}"
    text="${text//\{plan\}/$plan}"
    text="${text//\{session_pct\}/$session_pct}"
    text="${text//\{session_remaining_pct\}/$session_remaining_pct}"
    text="${text//\{session_reset\}/$s_cd_val}"
    text="${text//\{session_elapsed\}/$session_elapsed}"
    text="${text//\{weekly_pct\}/$weekly_pct}"
    text="${text//\{weekly_remaining_pct\}/$weekly_remaining_pct}"
    text="${text//\{weekly_reset\}/$w_cd_val}"
    text="${text//\{weekly_elapsed\}/$weekly_elapsed}"
    text="${text//\{review_pct\}/$review_pct}"
    text="${text//\{review_remaining_pct\}/$review_remaining_pct}"
    text="${text//\{review_reset\}/$r_cd_val}"
    text="${text//\{review_elapsed\}/$review_elapsed}"
    text="${text//\{has_review\}/$has_review}"
    text="${text//\{has_session\}/$has_session}"
    text="${text//\{has_weekly\}/$has_weekly}"
    text="${text//\{has_third\}/$has_third}"
    text="${text//\{third_label\}/$third_label}"
    text="${text//\{third_pct\}/$third_pct}"
    text="${text//\{third_reset\}/$t_cd_val}"
    text="${text//\{credits_balance\}/$credits_balance}"
    text="${text//\{credits_local\}/$credits_local}"
    text="${text//\{credits_cloud\}/$credits_cloud}"
    text="${text//\{session_bar\}/$format_session_bar}"
    text="${text//\{weekly_bar\}/$format_weekly_bar}"
    text="${text//\{review_bar\}/$format_review_bar}"
    text="${text//\{session_remaining_bar\}/$format_session_remaining_bar}"
    text="${text//\{weekly_remaining_bar\}/$format_weekly_remaining_bar}"
    text="${text//\{review_remaining_bar\}/$format_review_remaining_bar}"

    # Pace placeholders — pre-computed colored or plain depending on --format-pace-color.
    text="${text//\{session_pace_indicator\}/$_sp_session_indicator}"
    text="${text//\{session_pace_abs_delta\}/$_sp_session_abs_delta}"
    text="${text//\{session_pace_delta\}/$_sp_session_delta}"
    text="${text//\{session_pace_pct\}/$_sp_session_pct}"
    text="${text//\{session_pace_pts\}/$_sp_session_pts}"
    text="${text//\{session_pace\}/$_sp_session_pace}"
    text="${text//\{weekly_pace_indicator\}/$_sp_weekly_indicator}"
    text="${text//\{weekly_pace_abs_delta\}/$_sp_weekly_abs_delta}"
    text="${text//\{weekly_pace_delta\}/$_sp_weekly_delta}"
    text="${text//\{weekly_pace_pct\}/$_sp_weekly_pct}"
    text="${text//\{weekly_pace_pts\}/$_sp_weekly_pts}"
    text="${text//\{weekly_pace\}/$_sp_weekly_pace}"
    text="${text//\{review_pace_indicator\}/$_sp_review_indicator}"
    text="${text//\{review_pace_abs_delta\}/$_sp_review_abs_delta}"
    text="${text//\{review_pace_delta\}/$_sp_review_delta}"
    text="${text//\{review_pace_pct\}/$_sp_review_pct}"
    text="${text//\{review_pace_pts\}/$_sp_review_pts}"
    text="${text//\{review_pace\}/$_sp_review_pace}"

    printf '%s' "$text"
}

# Stale data indicator
stale_icon=""
[[ -f "$stale_marker" || "${transient_stale:-false}" == "true" ]] && stale_icon=" ⏸"

bar_text=$(apply_format "$FORMAT")${stale_icon}

# --- Colorize bar text via Pango ---
case "$class" in
    low)      bar_fg="$BAR_COLOR_LOW" ;;
    mid)      bar_fg="$BAR_COLOR_MID" ;;
    high)     bar_fg="$BAR_COLOR_HIGH" ;;
    critical) bar_fg="$BAR_COLOR_CRITICAL" ;;
    *)        bar_fg="$BAR_COLOR_LOW" ;;
esac
if [[ "$FORMAT_PACE_COLOR" == "true" && "$FORMAT" == *_pace* ]]; then
    # Neutral base; pace placeholders provide their own color
    bar_text="<span foreground='${FG}'>${BAR_ICON:+${BAR_ICON} }${bar_text}</span>"
else
    bar_text="<span foreground='${bar_fg}'>${BAR_ICON:+${BAR_ICON} }${bar_text}</span>"
fi

# --- Stale status note ---
# One explicit line saying WHY the ⏸ is shown and how old the data is. Used as
# the footer of the default tooltip and appended to custom tooltips (precedent:
# the ⏸ icon is already appended outside custom --format).
_updated="—"
_data_age=""
if [[ -f "$cache_file" ]]; then
    _updated=$(date -r "$cache_file" +%H:%M)
    _data_age=$(rel_age $(( $(date +%s) - $(date -r "$cache_file" +%s) )))
fi
_stale_note=""
if [[ "${transient_stale:-false}" == "true" ]]; then
    _stale_note="⏸  Waiting for network — data from ${_updated}${_data_age:+ (${_data_age})}"
elif [[ -f "$stale_marker" ]]; then
    _stale_note="⏸  Stale — data from ${_updated}${_data_age:+ (${_data_age})}"
fi

# --- Tooltip ---
if [[ -n "$TOOLTIP_FORMAT" ]]; then
    t=$(apply_format "$TOOLTIP_FORMAT")
    [[ -n "$_stale_note" ]] && t+=$'\n'"<span foreground='${ORANGE}'>${_stale_note}</span>"
else
    # Default rich Pango tooltip — tooltip-specific bars may carry an elapsed marker.
    if [[ "$TOOLTIP_PACE_PTS" == "true" ]]; then
        tip_session_bar=$(display_bar "$session_pct" "$session_elapsed" marker)
        tip_weekly_bar=$(display_bar "$weekly_pct" "$weekly_elapsed" marker)
    else
        tip_session_bar=$(display_bar "$session_pct" "$session_elapsed")
        tip_weekly_bar=$(display_bar "$weekly_pct" "$weekly_elapsed")
    fi
    s_cd="$s_cd_val"
    w_cd="$w_cd_val"

    NL=$'\n'
    B="<span foreground='${BLUE}'>"
    E="</span>"

    # shellcheck disable=SC2001  # regex replace; bash ${var//} patterns are globs
    strip_tags() { sed 's/<[^>]*>//g' <<< "$1"; }

    # Right-align a plain value label to a fixed column (width 6 fits "100% ↑").
    _val_pad() { local w=6 n=${#1}; (( n > w )) && w=$n; printf '%*s' $(( w - n )) ''; }

    # Collect all content lines into an array
    lines=()
    _tip_title="Codex ${plan}"
    [[ "$REMAINING" == "true" ]] && _tip_title="${_tip_title} · Remaining"
    lines+=("CENTER<span font_weight='bold' foreground='${BLUE}'>${_tip_title}</span>")
    lines+=("SEP")
    lines+=("")
    lines+=(" <span foreground='${FG}'>  󰔟  Session</span>")
    _lbl_s="$(display_pct "$session_pct")% ${_tip_session_pace}"
    lines+=("   ${tip_session_bar}  $(_val_pad "$_lbl_s")<span font_weight='bold' foreground='${session_color}'>${_lbl_s}</span>")
    lines+=(" <span foreground='${DIM}'>  󰥔  Resets in ${s_cd}</span>")
    lines+=("")
    lines+=(" <span foreground='${FG}'>  󰃰  Weekly</span>")
    _lbl_w="$(display_pct "$weekly_pct")% ${_tip_weekly_pace}"
    lines+=("   ${tip_weekly_bar}  $(_val_pad "$_lbl_w")<span font_weight='bold' foreground='${weekly_color}'>${_lbl_w}</span>")
    lines+=(" <span foreground='${DIM}'>  󰥔  Resets in ${w_cd}</span>")

    if [[ "$has_review" == "true" ]]; then
        if [[ "$TOOLTIP_PACE_PTS" == "true" ]]; then
            tip_review_bar=$(display_bar "$review_pct" "$review_elapsed" marker)
        else
            tip_review_bar=$(display_bar "$review_pct" "$review_elapsed")
        fi
        r_cd=$(countdown "$review_reset_at")
        lines+=("")
        lines+=(" <span foreground='${FG}'>  󰑕  Code review</span>")
        _lbl_r="$(display_pct "$review_pct")% ${_tip_review_pace}"
        lines+=("   ${tip_review_bar}  $(_val_pad "$_lbl_r")<span font_weight='bold' foreground='${review_color}'>${_lbl_r}</span>")
        lines+=(" <span foreground='${DIM}'>  󰥔  Resets in ${r_cd}</span>")
    fi

    if [[ -n "$additional_limits_tsv" ]]; then
        while IFS=$'\t' read -r add_name add_primary_pct add_primary_reset add_primary_window add_secondary_pct add_secondary_reset add_secondary_window; do
            [[ -z "${add_name:-}" ]] && continue
            add_primary_pct=${add_primary_pct:-0}
            add_primary_reset=${add_primary_reset:-0}
            add_primary_window=${add_primary_window:-0}
            add_secondary_pct=${add_secondary_pct:-0}
            add_secondary_reset=${add_secondary_reset:-0}
            add_secondary_window=${add_secondary_window:-0}

            # Only render (and thus count toward severity) meters with a displayable
            # window. additional_max_pct applies the same gate, so the worst-case
            # color can never come from a meter the tooltip doesn't show.
            (( add_primary_window > 0 || add_secondary_window > 0 )) || continue

            lines+=("")
            lines+=("SEP")
            lines+=(" <span foreground='${FG}'>  󱐌  $(pango_escape "$add_name")</span>")

            if (( add_primary_window > 0 )); then
                _add_primary_pace=$(calc_pacing "$add_primary_pct" "$add_primary_reset" "$add_primary_window")
                read -r add_primary_elapsed add_primary_pace add_primary_indicator _discard <<< "$_add_primary_pace"
                if [[ "$TOOLTIP_PACE_PTS" == "true" ]]; then
                    add_primary_tip_pace="$add_primary_indicator"
                    add_primary_bar=$(display_bar "$add_primary_pct" "$add_primary_elapsed" marker)
                else
                    add_primary_tip_pace="$add_primary_pace"
                    add_primary_bar=$(display_bar "$add_primary_pct" "$add_primary_elapsed")
                fi
                add_primary_label=$(window_label_for_seconds "$add_primary_window" 0)
                lines+=("")
                lines+=(" <span foreground='${FG}'>  󰔟  ${add_primary_label^}</span>")
                _lbl_ap="$(display_pct "$add_primary_pct")% ${add_primary_tip_pace}"
                lines+=("   ${add_primary_bar}  $(_val_pad "$_lbl_ap")<span font_weight='bold' foreground='$(color_for "$add_primary_pct")'>${_lbl_ap}</span>")
                lines+=(" <span foreground='${DIM}'>  󰥔  Resets in $(countdown "$add_primary_reset")</span>")
            fi

            if (( add_secondary_window > 0 )); then
                _add_secondary_pace=$(calc_pacing "$add_secondary_pct" "$add_secondary_reset" "$add_secondary_window")
                read -r add_secondary_elapsed add_secondary_pace add_secondary_indicator _discard <<< "$_add_secondary_pace"
                if [[ "$TOOLTIP_PACE_PTS" == "true" ]]; then
                    add_secondary_tip_pace="$add_secondary_indicator"
                    add_secondary_bar=$(display_bar "$add_secondary_pct" "$add_secondary_elapsed" marker)
                else
                    add_secondary_tip_pace="$add_secondary_pace"
                    add_secondary_bar=$(display_bar "$add_secondary_pct" "$add_secondary_elapsed")
                fi
                add_secondary_label=$(window_label_for_seconds "$add_secondary_window" 1)
                lines+=("")
                lines+=(" <span foreground='${FG}'>  󰃰  ${add_secondary_label^}</span>")
                _lbl_as="$(display_pct "$add_secondary_pct")% ${add_secondary_tip_pace}"
                lines+=("   ${add_secondary_bar}  $(_val_pad "$_lbl_as")<span font_weight='bold' foreground='$(color_for "$add_secondary_pct")'>${_lbl_as}</span>")
                lines+=(" <span foreground='${DIM}'>  󰥔  Resets in $(countdown "$add_secondary_reset")</span>")
            fi
        done <<< "$additional_limits_tsv"
    fi

    if [[ "$has_credits" == "true" ]]; then
        lines+=("")
        lines+=("SEP")
        lines+=(" <span foreground='${FG}'>  󰄑  Credits balance</span>  <span font_weight='bold' foreground='${FG}'>${credits_balance}</span>")
        if [[ "$credits_local" != "0" ]]; then
            lines+=(" <span foreground='${DIM}'>  󰀓  Local msgs: ~${credits_local}</span>")
        fi
        if [[ "$credits_cloud" != "0" ]]; then
            lines+=(" <span foreground='${DIM}'>  󰏗  Cloud msgs: ~${credits_cloud}</span>")
        fi
    fi

    # API error info (if stale)
    if [[ -f "$CACHE_DIR/.last_error" ]]; then
        _le_code="" _le_msg=""
        { read -r _le_code || true; read -r _le_msg || true; } < "$CACHE_DIR/.last_error" 2>/dev/null || true
        if [[ -n "$_le_code" ]] && [[ "$_le_code" =~ ^[0-9]+$ ]] && [[ "$_le_code" != "000" ]]; then
            _le_msg=$(sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' <<< "$_le_msg")
            if (( _le_code >= 500 )); then
                _err_icon="󰅚" _err_color="$RED"
            else
                _err_icon="󰀪" _err_color="$ORANGE"
            fi
            lines+=("")
            lines+=("SEP")
            _err_line=" <span foreground='${_err_color}'>  ${_err_icon}  HTTP ${_le_code}</span>"
            lines+=("$_err_line")
            if [[ -n "$_le_msg" ]]; then
                _wbuf=""
                for _wword in $_le_msg; do
                    if [[ -z "$_wbuf" ]]; then
                        _wbuf="$_wword"
                    elif (( ${#_wbuf} + 1 + ${#_wword} <= 35 )); then
                        _wbuf+=" $_wword"
                    else
                        lines+=("     <span foreground='${DIM}'>${_wbuf}</span>")
                        _wbuf="$_wword"
                    fi
                done
                [[ -n "$_wbuf" ]] && lines+=("     <span foreground='${DIM}'>${_wbuf}</span>")
            fi
        fi
    fi

    # Footer: explicit stale status, or "Updated HH:MM" when data is fresh.
    lines+=("")
    lines+=("SEP")
    if [[ -n "$_stale_note" ]]; then
        lines+=(" <span foreground='${ORANGE}'>  ${_stale_note}</span>")
    else
        lines+=(" <span foreground='${DIM}'>  󰅐  Updated ${_updated}</span>")
    fi

    # Calculate max width from content
    max_w=0
    for line in "${lines[@]}"; do
        [[ "$line" == "SEP" ]] && continue
        [[ "$line" == CENTER* ]] && line="${line#CENTER}"
        plain=$(strip_tags "$line")
        (( ${#plain} > max_w )) && max_w=${#plain}
    done
    INNER_W=$(( max_w + 1 ))

    pad() {
        local content="$1"
        local plain
        plain=$(strip_tags "$content")
        local len=${#plain}
        local need=$(( INNER_W - len ))
        (( need < 0 )) && need=0
        local sp=""
        printf -v sp '%*s' "$need" ''
        printf '%s%s' "$content" "$sp"
    }

    center_pad() {
        local content="$1"
        local plain
        plain=$(strip_tags "$content")
        local len=${#plain}
        local total=$(( INNER_W - len ))
        (( total < 0 )) && total=0
        local lp=$(( total / 2 )) rp=$(( total - total / 2 ))
        local ls="" rs=""
        printf -v ls '%*s' "$lp" ''
        printf -v rs '%*s' "$rp" ''
        printf '%s%s%s' "$ls" "$content" "$rs"
    }

    L() { printf '%s' "${B}│${E}$(pad "$1")${B}│${E}"; }
    LC() { printf '%s' "${B}│${E}$(center_pad "$1")${B}│${E}"; }

    # Build separator dynamically
    sep=""
    printf -v sep '%*s' "$(( INNER_W - 2 ))" ''; sep="${sep// /─}"
    sep=" <span foreground='${DIM}'>${sep}</span>"

    border_h=""
    printf -v border_h '%*s' "$INNER_W" ''; border_h="${border_h// /─}"

    # Render tooltip
    if [[ "$FRAME" == "true" ]]; then
        # Framed: draw the box; pinned to a Mono Nerd Font below so text,
        # box-drawing and icons share one uniform advance regardless of bar font.
        t=""
        t+="${B}╭${border_h}╮${E}${NL}"
        for line in "${lines[@]}"; do
            if [[ "$line" == "SEP" ]]; then
                t+="$(L "$sep")${NL}"
            elif [[ "$line" == CENTER* ]]; then
                t+="$(LC "${line#CENTER}")${NL}"
            else
                t+="$(L "$line")${NL}"
            fi
        done
        t+="${B}╰${border_h}╯${E}"
    else
        # Plain (default): no border, no font pin → renders in the user's font.
        t=""
        for line in "${lines[@]}"; do
            if [[ "$line" == "SEP" ]]; then
                t+="${sep}${NL}"
            elif [[ "$line" == CENTER* ]]; then
                # Leading space aligns the title with the rule and section headers.
                t+=" ${line#CENTER}${NL}"
            else
                t+="${line}${NL}"
            fi
        done
        t="${t%"$NL"}"
    fi
    if [[ "$FRAME" == "true" ]]; then
        _ff=$(sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/'\''/\&apos;/g' <<< "$FRAME_FONT")
        t="<span font_family='${_ff}'>${t}</span>"
    fi
fi

# --- Machine-readable account JSON (VGS widget bridge) ---
# Emits every additional_rate_limits[] lane (per-model quotas) rather than the
# single worst one the {third_*} placeholders can carry. Email comes straight
# from the usage response, so the widget can label accounts without the token.
if [[ "$EMIT_JSON" == "true" ]]; then
    # Codex reports reset instants as unix epochs already — claudebar converts
    # its ISO strings to the same shape so the widget only handles one format.
    s_epoch="${session_reset_at:-0}"; w_epoch="${weekly_reset_at:-0}"
    [[ "$s_epoch" =~ ^[0-9]+$ ]] || s_epoch=0
    [[ "$w_epoch" =~ ^[0-9]+$ ]] || w_epoch=0
    _cx_models=$(jq -c --argjson now "$(date +%s)" '
        def cd($at):
          if $at == null or $at == 0 then ""
          else ($at - $now) as $d
            | if $d <= 0 then "now"
              elif $d >= 86400 then "\($d / 86400 | floor)d \(($d % 86400) / 3600 | floor)h"
              elif $d >= 3600 then "\($d / 3600 | floor)h \(($d % 3600) / 60 | floor)m"
              else "\($d / 60 | floor)m" end
          end;
        [ (.additional_rate_limits // [])[]
          | select(type == "object")
          | { label: ((.limit_name // .metered_feature // "Extra") | tostring),
              pct: (((.rate_limit.primary_window.used_percent | numbers) // 0) | round),
              reset: cd((.rate_limit.primary_window.reset_at | numbers) // 0),
              resetAt: ((.rate_limit.primary_window.reset_at | numbers) // 0),
              severity: "normal" }
        ]' <<< "$usage" 2>/dev/null)
    [[ -n "$_cx_models" ]] || _cx_models="[]"
    _cx_email=$(jq -r '.email // empty' <<< "$usage" 2>/dev/null)
    if [[ "$has_session" != "true" && "$has_weekly" != "true" && "$_cx_models" == "[]" ]]; then
        jq -nc --arg config_dir "$CONFIG_DIR" \
            '{ok:false, error:"no usage quota reported for this account", configDir:$config_dir}'
        exit 0
    fi
    jq -nc \
        --arg plan "$plan" --arg class "$class" --arg email "$_cx_email" \
        --arg config_dir "$CONFIG_DIR" \
        --argjson has_session "$has_session" --argjson session_pct "$session_pct" --arg session_reset "$s_cd_val" \
        --argjson has_weekly "$has_weekly" --argjson weekly_pct "$weekly_pct" --arg weekly_reset "$w_cd_val" \
        --argjson session_reset_at "$s_epoch" --argjson weekly_reset_at "$w_epoch" \
        --argjson models "$_cx_models" \
        '{ok:true, plan:$plan, class:$class, email:$email, configDir:$config_dir,
          session: (if $has_session then {pct:$session_pct, reset:$session_reset, resetAt:$session_reset_at} else null end),
          weekly:  (if $has_weekly  then {pct:$weekly_pct,  reset:$weekly_reset,  resetAt:$weekly_reset_at}  else null end),
          models: $models}'
    exit 0
fi

# --- Output waybar JSON ---
jq -nc --arg text "$bar_text" --arg tooltip "$t" --arg class "$class" \
    '{text:$text, tooltip:$tooltip, class:$class}'
