oxios 1.13.1

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { AlertCircle, CheckCircle2, Clock, Wallet } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Badge } from '@/components/ui/badge'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useTokenMaxingProviders, useTokenMaxingStatus } from '@/hooks/use-token-maxing'
import {
  type AvailabilityVerdict,
  type NormalizedAvailability,
  normalizeAvailability,
} from '@/types/token-maxing'

/** Live provider availability panel — RFC-031 §4 verdict per provider.
 *  Mirrors ProviderQuotaCards structure (Card → list of rows with
 *  verdict badge + secondary line for counter details).
 */
export function TokenMaxingProviderCards() {
  const { t } = useTranslation()
  // /providers has the authoritative per-provider list and recalibration/
  // cooldown history; /status has the same providers plus live session info.
  // Either is fine — use /providers for this panel.
  const { data, isLoading } = useTokenMaxingProviders()
  const status = useTokenMaxingStatus()

  const providers = data?.providers ?? status.data?.providers ?? []

  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex items-center gap-2 text-base">
          <Wallet className="h-4 w-4" />
          {t('tokenMaxing.providers.title')}
        </CardTitle>
      </CardHeader>
      <CardContent>
        {isLoading && providers.length === 0 ? (
          <p className="text-sm text-muted-foreground py-4">{t('common.loading')}</p>
        ) : providers.length === 0 ? (
          <p className="text-sm text-muted-foreground py-4">{t('tokenMaxing.providers.empty')}</p>
        ) : (
          <div className="space-y-3">
            {providers.map((p) => {
              const norm = normalizeAvailability(p.availability)
              return <ProviderRow key={p.provider} provider={p.provider} availability={norm} />
            })}
          </div>
        )}
      </CardContent>
    </Card>
  )
}

function ProviderRow({
  provider,
  availability,
}: {
  provider: string
  availability: NormalizedAvailability
}) {
  return (
    <div className="flex items-center justify-between rounded-lg border p-3">
      <div className="space-y-1 min-w-0">
        <div className="flex items-center gap-2">
          <span className="text-sm font-medium">{provider}</span>
          <VerdictBadge verdict={availability.verdict} />
        </div>
        <ProviderDetail availability={availability} />
      </div>
    </div>
  )
}

function VerdictBadge({ verdict }: { verdict: AvailabilityVerdict }) {
  const { t } = useTranslation()
  switch (verdict) {
    case 'available':
      return (
        <Badge variant="success" className="gap-1 text-xs">
          <CheckCircle2 className="h-3 w-3" />
          {t('tokenMaxing.providers.verdict.available')}
        </Badge>
      )
    case 'draining':
      return (
        <Badge variant="warning" className="gap-1 text-xs">
          <AlertCircle className="h-3 w-3" />
          {t('tokenMaxing.providers.verdict.draining')}
        </Badge>
      )
    case 'cooled_down':
      return (
        <Badge variant="error" className="gap-1 text-xs">
          <Clock className="h-3 w-3" />
          {t('tokenMaxing.providers.verdict.cooledDown')}
        </Badge>
      )
    default:
      return (
        <Badge variant="outline" className="text-xs">
          {t('tokenMaxing.providers.verdict.ineligible')}
        </Badge>
      )
  }
}

function ProviderDetail({ availability }: { availability: NormalizedAvailability }) {
  const { t } = useTranslation()
  const snap = availability.snapshot

  if (availability.verdict === 'ineligible') {
    return <p className="text-xs text-muted-foreground">{t('tokenMaxing.providers.notEligible')}</p>
  }

  if (availability.verdict === 'cooled_down') {
    return (
      <div className="flex items-center gap-3 text-xs text-muted-foreground">
        {availability.until && (
          <span>
            {t('tokenMaxing.providers.cooldownUntil', {
              time: formatDateTime(availability.until),
            })}
          </span>
        )}
        {availability.reason && (
          <span>
            {t('tokenMaxing.providers.cooldownReason', {
              reason: availability.reason,
            })}
          </span>
        )}
      </div>
    )
  }

  // Available or Draining — show self-tracked counter snapshot.
  if (!snap) {
    return <p className="text-xs text-muted-foreground">{t('tokenMaxing.providers.noSnapshot')}</p>
  }

  const remaining = snap.remaining_percent
  return (
    <div className="flex items-center gap-4 text-xs text-muted-foreground">
      <span>
        {t('tokenMaxing.providers.tokensUsed', {
          used: snap.tokens_used.toLocaleString(),
          limit: snap.token_limit.toLocaleString(),
        })}
      </span>
      {remaining != null && (
        <span>
          {t('tokenMaxing.providers.remaining', {
            percent: remaining.toFixed(1),
          })}
        </span>
      )}
      {snap.resets_at && (
        <span>
          {t('tokenMaxing.providers.resetsAt', {
            time: formatDateTime(snap.resets_at),
          })}
        </span>
      )}
    </div>
  )
}

function formatDateTime(iso: string): string {
  const d = new Date(iso)
  if (Number.isNaN(d.getTime())) return iso
  return d.toLocaleString()
}