oxios 1.13.1

Oxios Agent OS — Agent Operating System powered by oxi-sdk
import { CheckCircle2, Clock, FileText, ListChecks, XCircle } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { EmptyState } from '@/components/shared/empty-state'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { useTokenMaxingSession, useTokenMaxingSessions } from '@/hooks/use-token-maxing'
import type { StopReason, TaskRecord, TokenMaxingSession } from '@/types/token-maxing'

/** Past sessions list — clicking a row opens the full report in a Dialog. */
export function TokenMaxingSessions() {
  const { t } = useTranslation()
  const { data, isLoading } = useTokenMaxingSessions()
  const [openId, setOpenId] = useState<string | null>(null)

  const sessions = data ?? []

  return (
    <Card>
      <CardHeader>
        <CardTitle className="flex items-center gap-2 text-base">
          <ListChecks className="h-4 w-4" />
          {t('tokenMaxing.sessions.title')}
        </CardTitle>
      </CardHeader>
      <CardContent>
        {isLoading ? (
          <p className="text-sm text-muted-foreground py-4">{t('common.loading')}</p>
        ) : sessions.length === 0 ? (
          <EmptyState
            icon={<FileText className="h-10 w-10" />}
            title={t('tokenMaxing.sessions.emptyTitle')}
            description={t('tokenMaxing.sessions.emptyDesc')}
          />
        ) : (
          <div className="space-y-2">
            {sessions
              .slice()
              .reverse()
              .map((s) => (
                <SessionRow key={s.id} session={s} onOpen={() => setOpenId(s.id)} />
              ))}
          </div>
        )}
      </CardContent>

      <SessionReportDialog sessionId={openId} onClose={() => setOpenId(null)} />
    </Card>
  )
}

function SessionRow({ session, onOpen }: { session: TokenMaxingSession; onOpen: () => void }) {
  const { t } = useTranslation()
  const ended = session.ended_at != null
  return (
    <Button
      variant="outline"
      onClick={onOpen}
      className="w-full justify-between h-auto py-3 px-4 text-left whitespace-normal"
    >
      <div className="space-y-1 min-w-0">
        <div className="flex items-center gap-2">
          <span className="text-sm font-medium">{formatDateTime(session.started_at)}</span>
          <StopReasonBadge reason={session.stop_reason} ended={ended} />
          {session.manual && (
            <Badge variant="outline" className="text-xs">
              {t('tokenMaxing.status.manual')}
            </Badge>
          )}
        </div>
        <div className="flex items-center gap-4 text-xs text-muted-foreground">
          <span>
            {t('tokenMaxing.sessions.tasksAndTokens', {
              tasks: session.totals.tasks,
              tokens: session.totals.tokens.toLocaleString(),
            })}
          </span>
          <span>
            {t('tokenMaxing.sessions.drained', {
              count: session.totals.providers_fully_drained,
            })}
          </span>
          <span>
            {t('tokenMaxing.sessions.resets', {
              count: session.totals.resets_observed,
            })}
          </span>
        </div>
      </div>
    </Button>
  )
}

function StopReasonBadge({ reason, ended }: { reason: StopReason; ended: boolean }) {
  const { t } = useTranslation()
  if (!ended) {
    return (
      <Badge variant="success" className="text-xs">
        {t('tokenMaxing.sessions.inProgress')}
      </Badge>
    )
  }
  switch (reason) {
    case 'window_ended':
      return (
        <Badge variant="secondary" className="text-xs">
          {t('tokenMaxing.stopReason.windowEnded')}
        </Badge>
      )
    case 'no_work':
      return (
        <Badge variant="secondary" className="text-xs">
          {t('tokenMaxing.stopReason.noWork')}
        </Badge>
      )
    case 'cancelled':
      return (
        <Badge variant="warning" className="text-xs">
          {t('tokenMaxing.stopReason.cancelled')}
        </Badge>
      )
    default:
      return (
        <Badge variant="outline" className="text-xs">
          {t('tokenMaxing.sessions.unknownStop')}
        </Badge>
      )
  }
}

function SessionReportDialog({
  sessionId,
  onClose,
}: {
  sessionId: string | null
  onClose: () => void
}) {
  const { t } = useTranslation()
  const { data, isLoading, isError } = useTokenMaxingSession(sessionId)

  return (
    <Dialog open={sessionId != null} onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="max-w-3xl max-h-[85vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>{t('tokenMaxing.report.title')}</DialogTitle>
        </DialogHeader>

        {sessionId == null ? null : isLoading ? (
          <p className="text-sm text-muted-foreground py-4">{t('common.loading')}</p>
        ) : isError || !data ? (
          <p className="text-sm text-error py-4">{t('tokenMaxing.report.loadFailed')}</p>
        ) : (
          <SessionReportBody session={data} />
        )}
      </DialogContent>
    </Dialog>
  )
}

function SessionReportBody({ session }: { session: TokenMaxingSession }) {
  const { t } = useTranslation()
  return (
    <div className="space-y-6">
      <SessionMeta session={session} />

      <div>
        <h3 className="text-sm font-medium mb-2">{t('tokenMaxing.report.totals')}</h3>
        <div className="grid gap-3 sm:grid-cols-4">
          <Stat
            label={t('tokenMaxing.report.tasks')}
            value={session.totals.tasks.toLocaleString()}
          />
          <Stat
            label={t('tokenMaxing.report.tokens')}
            value={session.totals.tokens.toLocaleString()}
          />
          <Stat
            label={t('tokenMaxing.report.drained')}
            value={session.totals.providers_fully_drained.toLocaleString()}
          />
          <Stat
            label={t('tokenMaxing.report.resets')}
            value={session.totals.resets_observed.toLocaleString()}
          />
        </div>
      </div>

      <div>
        <h3 className="text-sm font-medium mb-2">{t('tokenMaxing.report.perProvider')}</h3>
        {session.providers.length === 0 ? (
          <p className="text-sm text-muted-foreground">{t('tokenMaxing.report.noProviderData')}</p>
        ) : (
          <div className="space-y-2">
            {session.providers.map((p) => (
              <div key={p.provider} className="rounded-md border p-3 space-y-1">
                <div className="flex items-center justify-between">
                  <span className="text-sm font-medium">{p.provider}</span>
                  <span className="text-xs text-muted-foreground">
                    {t('tokenMaxing.report.tasksAndTokens', {
                      tasks: p.tasks_run,
                      tokens: p.tokens_consumed.toLocaleString(),
                    })}
                  </span>
                </div>
                {p.models_used.length > 0 && (
                  <p className="text-xs text-muted-foreground">
                    {t('tokenMaxing.report.models', {
                      models: p.models_used.join(', '),
                    })}
                  </p>
                )}
                {p.windows_drained.length > 0 && (
                  <p className="text-xs text-muted-foreground">
                    {t('tokenMaxing.report.windowsDrained', {
                      count: p.windows_drained.length,
                    })}
                  </p>
                )}
              </div>
            ))}
          </div>
        )}
      </div>

      <div>
        <h3 className="text-sm font-medium mb-2">{t('tokenMaxing.report.taskList')}</h3>
        {session.tasks.length === 0 ? (
          <p className="text-sm text-muted-foreground">{t('tokenMaxing.report.noTasks')}</p>
        ) : (
          <div className="space-y-2">
            {session.tasks.map((task, i) => (
              <TaskRow key={`${task.provider}-${i}`} task={task} />
            ))}
          </div>
        )}
      </div>
    </div>
  )
}

function SessionMeta({ session }: { session: TokenMaxingSession }) {
  const { t } = useTranslation()
  return (
    <div className="grid gap-2 sm:grid-cols-2 text-sm">
      <MetaRow label={t('tokenMaxing.report.startedAt')}>
        {formatDateTime(session.started_at)}
      </MetaRow>
      <MetaRow label={t('tokenMaxing.report.endedAt')}>
        {session.ended_at ? formatDateTime(session.ended_at) : '—'}
      </MetaRow>
      <MetaRow label={t('tokenMaxing.report.window')}>
        {session.window
          ? `${formatDateTime(session.window.start)} → ${formatDateTime(session.window.end)}`
          : t('tokenMaxing.status.manual')}
      </MetaRow>
      <MetaRow label={t('tokenMaxing.report.stopReason')}>
        <StopReasonBadge reason={session.stop_reason} ended={session.ended_at != null} />
      </MetaRow>
    </div>
  )
}

function MetaRow({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div className="flex items-center gap-2 text-xs">
      <span className="text-muted-foreground">{label}</span>
      <span>{children}</span>
    </div>
  )
}

function Stat({ label, value }: { label: string; value: string }) {
  return (
    <div className="rounded-md border p-3 space-y-1">
      <p className="text-xs text-muted-foreground">{label}</p>
      <p className="text-lg font-semibold">{value}</p>
    </div>
  )
}

function TaskRow({ task }: { task: TaskRecord }) {
  const { t } = useTranslation()
  return (
    <div className="rounded-md border p-3 space-y-1">
      <div className="flex items-center gap-2 flex-wrap">
        {task.success ? (
          <CheckCircle2 className="h-4 w-4 text-success" />
        ) : (
          <XCircle className="h-4 w-4 text-error" />
        )}
        <Badge variant="outline" className="text-xs">
          {t(`tokenMaxing.report.source.${task.source}`)}
        </Badge>
        <span className="text-xs text-muted-foreground">
          {task.provider}/{task.model}
        </span>
        <span className="text-xs text-muted-foreground">
          {t('tokenMaxing.report.tokensFmt', {
            tokens: task.tokens.toLocaleString(),
          })}
        </span>
        <span className="text-xs text-muted-foreground flex items-center gap-1">
          <Clock className="h-3 w-3" />
          {t('tokenMaxing.report.duration', {
            seconds: task.duration_secs.toFixed(1),
          })}
        </span>
      </div>
      <p className="text-sm font-medium">{task.goal}</p>
      {task.summary && (
        <p className="text-xs text-muted-foreground whitespace-pre-wrap">{task.summary}</p>
      )}
    </div>
  )
}

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