import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
import { FormEvent, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { HistoryDataResponse, HistoryDataRow, HistoryExportRequest, HistoryMetricDefinition, HistorySeriesResponse } from '../../api/types';
import { buildHistorySeriesPanels, formatExportFileSize, formatHistoryValue, formatSeriesGrain, historyExportPollInterval, parseHistoryKeywords } from '../domain/history';
import { downloadBlob } from '../domain/download';
import { InlineError } from '../shared/AsyncState';
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
import { LIVE_QUERY_POLICY, QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
import { usePlatformSession } from '../auth/AuthGate';
import { canOperate } from '../auth/session';
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';

const historyAxisTimeFormatter = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false });

function historyQualityLabel(quality: string) {
  if (quality === 'normal' || quality === 'good') return '正常';
  if (quality === 'warning') return '需关注';
  if (quality === 'error' || quality === 'critical') return '异常';
  return quality || '未知';
}

function HistoryTrend({ response, category, loading, error, hasMetrics }: { response?: HistorySeriesResponse; category: string; loading: boolean; error?: string; hasMetrics: boolean }) {
  const panels = useMemo(() => buildHistorySeriesPanels(response), [response]);
  if (category !== 'location') return <section className="v2-history-trend"><header><strong>聚合趋势</strong></header><div className="v2-history-chart-empty">{category === 'raw' ? '原始报文是离散证据，不生成可能误导的连续趋势；请使用明细与导出。' : '日里程按自然日展示，当前请使用明细表核对起止里程。'}</div></section>;
  if (!hasMetrics) return <section className="v2-history-trend"><header><strong>聚合趋势</strong></header><div className="v2-history-chart-empty">在指标字段中选中“速度”或“总里程”后展示趋势。</div></section>;
  const summary = response?.summary;
  const coverage = summary?.expectedBucketCount ? Math.max(0, (summary.expectedBucketCount - summary.missingBucketCount) / summary.expectedBucketCount * 100) : 0;
  return <section className="v2-history-trend"><header><strong>聚合趋势</strong><div>{summary ? <><span>{formatSeriesGrain(summary.grainSeconds)}粒度</span><span>覆盖 {coverage.toFixed(1)}%</span><span>{summary.rawPointCount.toLocaleString('zh-CN')} 原始点</span></> : null}</div></header>
    {error ? <div className="v2-history-chart-empty">趋势加载失败：{error}</div> : loading && !response ? <div className="v2-history-chart-empty">正在聚合时间序列…</div> : panels.length ? <div className="v2-history-trend-panels">{panels.map((panel) => <article key={panel.key}><header><strong>{panel.label}</strong><span>{panel.unit || '数值'} · {panel.lines.reduce((sum, line) => sum + line.points, 0)} 个桶点</span></header><svg viewBox="0 0 800 116" role="img" aria-label={`${panel.label}按时间变化趋势`}>
      <g className="v2-chart-grid"><line x1="54" y1="10" x2="786" y2="10" /><line x1="54" y1="53" x2="786" y2="53" /><line x1="54" y1="96" x2="786" y2="96" /></g>
      <g className="v2-chart-axis"><text x="49" y="14">{panel.maximum.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}</text><text x="49" y="100">{panel.minimum.toLocaleString('zh-CN', { maximumFractionDigits: 2 })}</text><text x="54" y="112">{formatAxisTime(panel.start)}</text><text x="786" y="112" textAnchor="end">{formatAxisTime(panel.end)}</text></g>
      {panel.lines.flatMap((line) => line.paths.map((path, index) => <path key={`${line.key}-${index}`} d={path} fill="none" stroke={line.color} strokeWidth="2" vectorEffect="non-scaling-stroke"><title>{line.label}</title></path>))}
    </svg><footer>{panel.lines.map((line) => <span key={line.key}><i style={{ background: line.color }} />{line.label}</span>)}</footer></article>)}</div> : <div className="v2-history-chart-empty">当前时间窗没有可聚合的数值点；空窗未补值。</div>}
    {summary ? <small className="v2-history-trend-evidence">{summary.evidence} · 缺失 {summary.missingBucketCount.toLocaleString('zh-CN')} / {summary.expectedBucketCount.toLocaleString('zh-CN')} 桶 · 查询 {summary.queryDurationMs} ms</small> : null}
  </section>;
}

export function formatAxisTime(value: string) {
  const parsed = new Date(value);
  if (!Number.isFinite(parsed.getTime())) return value.replace('T', ' ').slice(5, 16);
  return historyAxisTimeFormatter.format(parsed).replace('/', '-');
}
function currentHistoryWindow() {
  const now = new Date(); const pad = (value: number) => String(value).padStart(2, '0');
  const day = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
  return { dateFrom: `${day}T00:00`, dateTo: `${day}T${pad(now.getHours())}:${pad(now.getMinutes())}` };
}

function CreateExportButton({ request, disabled }: { request: HistoryExportRequest; disabled: boolean }) {
  const queryClient = useQueryClient();
  const mutation = useMutation({ mutationFn: api.createHistoryExport, onSuccess: () => queryClient.invalidateQueries({ queryKey: ['history-exports'] }) });
  const label = mutation.isPending ? '任务排队中' : mutation.isError ? '导出失败，重试' : '创建导出';
  return <button className="v2-secondary-button" type="button" disabled={disabled || mutation.isPending} title={mutation.error instanceof Error ? mutation.error.message : '最多 100 万行；任务按创建顺序单并发流式执行'} onClick={() => mutation.mutate(request)}><IconDownload />{label}</button>;
}

function ExportDownloadButton({ id }: { id: string }) {
  const mutation = useMutation({
    mutationFn: () => api.downloadHistoryExport(id),
    onSuccess: ({ blob, filename }) => downloadBlob(blob, filename)
  });
  return <button type="button" className="v2-export-download" disabled={mutation.isPending} title={mutation.error instanceof Error ? mutation.error.message : '下载当前账号有权访问的导出文件'} onClick={() => mutation.mutate()}><IconDownload />{mutation.isPending ? '下载中' : mutation.isError ? '重试' : '下载'}</button>;
}

function ExportJobsPanel() {
  const query = useQuery({
    queryKey: ['history-exports'],
    queryFn: ({ signal }) => api.historyExports(signal),
    refetchInterval: (current) => historyExportPollInterval(current.state.data),
    ...LIVE_QUERY_POLICY,
    gcTime: QUERY_MEMORY.highVolumeGcTime
  });
  const jobs = query.data ?? [];
  return <section className="v2-export-jobs"><header><strong>导出任务</strong><span>{jobs.length}</span></header><div>{jobs.slice(0, 6).map((job) => <article key={job.id} title={job.evidence}><i className={`is-${job.status}`} /><div><strong>{job.name}</strong><small>{job.status === 'queued' ? '等待单并发执行' : job.status === 'running' ? `${job.processedRows.toLocaleString('zh-CN')} / ${job.totalRows.toLocaleString('zh-CN')} 行 · ${job.progress}%` : job.status === 'completed' ? `${job.rowCount.toLocaleString('zh-CN')} 行 · ${formatExportFileSize(job.fileSizeBytes)} · 已完成` : job.error || '失败'}</small><small className="v2-export-owner">{job.ownerUsername || job.ownerName || '历史任务'} · {job.vehicleVins?.length ?? job.keywords.length} 辆 · {job.dateFrom && job.dateTo ? `${job.dateFrom.replace('T', ' ')} 至 ${job.dateTo.replace('T', ' ')}` : job.createdAt}</small></div>{job.downloadUrl ? <ExportDownloadButton id={job.id} /> : <em>{job.status === 'running' ? `${job.progress}%` : '—'}</em>}</article>)}{query.isError ? <div className="v2-history-side-empty">导出任务加载失败</div> : !jobs.length ? <div className="v2-history-side-empty">当前账号尚未创建导出任务</div> : null}</div></section>;
}

function EvidencePanel({ row, metrics, onClose }: { row?: HistoryDataRow; metrics: HistoryMetricDefinition[]; onClose: () => void }) {
  return <section className="v2-history-evidence"><header><strong>数据详情</strong>{row ? <button onClick={onClose} type="button" aria-label="关闭数据详情"><IconClose /></button> : null}</header>
    {row ? <><dl><div><dt>车牌</dt><dd>{row.plate || '—'}</dd></div><div><dt>VIN</dt><dd>{row.vin}</dd></div><div><dt>设备时间</dt><dd>{row.deviceTime}</dd></div><div><dt>服务时间</dt><dd>{row.serverTime}</dd></div><div><dt>数据来源</dt><dd>{row.protocol}</dd></div><div><dt>数据质量</dt><dd><i className={`is-${row.quality}`} />{historyQualityLabel(row.quality)}</dd></div><div className="v2-history-quality-detail"><dt>质量原因</dt><dd>{row.qualityReason || '平台未返回质量说明'}</dd></div></dl><div className="v2-evidence-values"><strong>已选字段</strong>{metrics.length ? metrics.slice(0, 20).map((metric) => <div key={metric.key}><span>{metric.label}<small>{metric.key}</small></span><b>{formatHistoryValue(row.values[metric.key], metric)}</b></div>) : <p>当前没有选中业务字段</p>}</div>{row.evidenceId ? <footer><span>RAW 证据</span><b>{row.evidenceId}</b></footer> : null}</> : <div className="v2-history-side-empty">选择一行查看来源、质量原因和字段详情。</div>}
  </section>;
}

function ColumnVisibilityPanel({ metrics, visibleKeys, onToggle, onShowAll, onReset, onClose }: {
  metrics: HistoryMetricDefinition[];
  visibleKeys: string[];
  onToggle: (key: string) => void;
  onShowAll: () => void;
  onReset: () => void;
  onClose: () => void;
}) {
  const [search, setSearch] = useState('');
  const filteredMetrics = useMemo(() => {
    const keyword = search.trim().toLowerCase();
    if (!keyword) return metrics;
    return metrics.filter((metric) => `${metric.label} ${metric.key} ${metric.unit}`.toLowerCase().includes(keyword));
  }, [metrics, search]);
  const displayedMetrics = filteredMetrics.slice(0, 200);
  useEffect(() => {
    const closeOnEscape = (event: KeyboardEvent) => { if (event.key === 'Escape') onClose(); };
    window.addEventListener('keydown', closeOnEscape);
    return () => window.removeEventListener('keydown', closeOnEscape);
  }, [onClose]);
  return <aside className="v2-history-column-panel" role="dialog" aria-modal="false" aria-label="列显示设置">
    <header><div><strong>列显示设置</strong><span>基础身份与时间列保持显示</span></div><button type="button" aria-label="关闭列显示设置" onClick={onClose}><IconClose /></button></header>
    <label className="v2-history-column-search"><IconSearch /><input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="搜索字段名 / 字段 key" /></label>
    <div>{displayedMetrics.map((metric) => <label key={metric.key}><input type="checkbox" checked={visibleKeys.includes(metric.key)} onChange={() => onToggle(metric.key)} /><span>{metric.label}{metric.unit ? ` (${metric.unit})` : ''}</span><small>{metric.key}</small></label>)}{!metrics.length ? <p>当前数据类型没有可配置指标</p> : search && !filteredMetrics.length ? <p>没有匹配字段</p> : filteredMetrics.length > displayedMetrics.length ? <p>当前仅展示前 200 项，请输入字段名或 key 缩小范围。</p> : null}</div>
    <footer><span>已选 {visibleKeys.length} / {metrics.length}</span><button type="button" onClick={onShowAll} disabled={!metrics.length}>显示全部</button><button type="button" onClick={onReset} disabled={!metrics.length}>恢复默认</button></footer>
  </aside>;
}

export default function HistoryPage() {
  const { session } = usePlatformSession();
  const exportAllowed = canOperate(session) || session.role === 'customer';
  const [searchParams, setSearchParams] = useSearchParams();
  const monitorReturn = monitorReturnFromParams(searchParams);
  const today = useMemo(currentHistoryWindow, []);
  const initial = { keywords: searchParams.get('vin') || searchParams.get('keywords') || '', dateFrom: searchParams.get('dateFrom') || today.dateFrom, dateTo: searchParams.get('dateTo') || today.dateTo, category: searchParams.get('category') || 'location', protocol: searchParams.get('protocol') || '' };
  const [draft, setDraft] = useState(initial);
  const [criteria, setCriteria] = useState(initial);
  const [offset, setOffset] = useState(0);
  const [limit, setLimit] = useState(50);
  const [visibleByCategory, setVisibleByCategory] = useState<Record<string, string[]>>({});
  const [selectedRowRef, setSelectedRowRef] = useState<{ scope: string; id: string }>();
  const [density, setDensity] = useState<'compact' | 'comfortable'>('compact');
  const [columnSettingsOpen, setColumnSettingsOpen] = useState(false);
  const [filtersCollapsed, setFiltersCollapsed] = useState(true);
  const keywords = useMemo(() => parseHistoryKeywords(criteria.keywords), [criteria.keywords]);
  const params = useMemo(() => {
    const next = new URLSearchParams({ keywords: keywords.join(','), category: criteria.category, limit: String(limit), offset: String(offset) });
    if (criteria.dateFrom) next.set('dateFrom', criteria.dateFrom);
    if (criteria.dateTo) next.set('dateTo', criteria.dateTo);
    if (criteria.protocol) next.set('protocol', criteria.protocol);
    return next;
  }, [criteria, keywords, limit, offset]);
  const dataScope = useMemo(() => {
    const scope = new URLSearchParams(params);
    scope.delete('limit');
    scope.delete('offset');
    return scope.toString();
  }, [params]);
  const catalogQuery = useQuery({ queryKey: ['history-metric-catalog'], queryFn: ({ signal }) => api.historyMetricCatalog(signal), staleTime: 30 * 60_000 });
  const dataQuery = useQuery<HistoryDataResponse>({
    queryKey: ['history-data', dataScope, limit, offset],
    enabled: keywords.length > 0,
    queryFn: ({ signal }) => api.historyData(params, signal),
    placeholderData: retainPreviousPageWithinScope<HistoryDataResponse>(dataScope),
    gcTime: QUERY_MEMORY.highVolumeGcTime
  });
  const result = dataQuery.data;
  const allMetrics = result?.columns ?? catalogQuery.data?.metrics.filter((metric) => metric.category === criteria.category) ?? [];
  const visibleKeys = visibleByCategory[criteria.category] ?? allMetrics.filter((metric) => metric.defaultVisible).map((metric) => metric.key);
  const visibleMetrics = allMetrics.filter((metric) => visibleKeys.includes(metric.key));
  const seriesMetricKey = visibleKeys.filter((key) => key === 'speedKmh' || key === 'totalMileageKm').join(',');
  const seriesParams = useMemo(() => {
    const next = new URLSearchParams({ keywords: keywords.join(','), category: criteria.category, metrics: seriesMetricKey, targetPoints: '240' });
    if (criteria.dateFrom) next.set('dateFrom', criteria.dateFrom);
    if (criteria.dateTo) next.set('dateTo', criteria.dateTo);
    if (criteria.protocol) next.set('protocol', criteria.protocol);
    return next;
  }, [criteria, keywords, seriesMetricKey]);
  const seriesQuery = useQuery({ queryKey: ['history-series', seriesParams.toString()], enabled: keywords.length > 0 && criteria.category === 'location' && Boolean(seriesMetricKey), queryFn: ({ signal }) => api.historySeries(seriesParams, signal), gcTime: QUERY_MEMORY.highVolumeGcTime });

  const selectedRow = selectedRowRef?.scope === dataScope ? result?.rows.find((row) => row.id === selectedRowRef.id) : undefined;
  useEffect(() => {
    const first = result?.rows[0];
    setSelectedRowRef(first ? { scope: dataScope, id: first.id } : undefined);
  }, [dataScope, result]);

  const submit = (event: FormEvent) => {
    event.preventDefault();
    const parsed = parseHistoryKeywords(draft.keywords);
    if (!parsed.length) return;
    const next = { ...draft, keywords: parsed.join(',') };
    setCriteria(next); setOffset(0);
    setFiltersCollapsed(true);
    const url = new URLSearchParams({ keywords: next.keywords, category: next.category });
    if (next.dateFrom) url.set('dateFrom', next.dateFrom);
    if (next.dateTo) url.set('dateTo', next.dateTo);
    if (next.protocol) url.set('protocol', next.protocol);
    setSearchParams(preserveMonitorReturn(url, monitorReturn), { replace: true });
  };
  const reset = () => { const next = { keywords: '', ...currentHistoryWindow(), category: 'location', protocol: '' }; setDraft(next); setCriteria(next); setOffset(0); setSearchParams(preserveMonitorReturn(new URLSearchParams(), monitorReturn), { replace: true }); };
  const toggleMetric = (key: string) => setVisibleByCategory((current) => {
    const baseline = current[criteria.category] ?? allMetrics.filter((metric) => metric.defaultVisible).map((metric) => metric.key);
    const next = baseline.includes(key) ? baseline.filter((item) => item !== key) : [...baseline, key];
    return { ...current, [criteria.category]: next };
  });
  const setVisibleMetrics = (keys: string[]) => setVisibleByCategory((current) => ({ ...current, [criteria.category]: keys }));
  const totalPages = Math.max(1, Math.ceil((result?.total ?? 0) / limit));
  const page = Math.floor(offset / limit) + 1;

  return <div className="v2-history-page">
    <MonitorReturnBar />
    <button type="button" className="v2-mobile-filter-toggle" aria-expanded={!filtersCollapsed} onClick={() => setFiltersCollapsed((value) => !value)}><span><b>查询条件</b><small>{keywords.length ? `${keywords.length} 辆 · ${criteria.category === 'location' ? '位置数据' : criteria.category === 'raw' ? '原始报文' : '日里程'}` : '请选择车辆'}</small></span><em>{filtersCollapsed ? '修改' : '收起'}</em></button>
    <form className={`v2-history-toolbar${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}>
      <label className="v2-history-vehicles"><span>车辆（最多 5 台）</span><div><IconSearch /><input value={draft.keywords} onChange={(event) => setDraft((value) => ({ ...value, keywords: event.target.value }))} placeholder="车牌 / VIN，多台用逗号分隔" /></div></label>
      <fieldset className="v2-history-range"><legend>时间范围</legend><div><input aria-label="开始时间" type="datetime-local" value={draft.dateFrom} onChange={(event) => setDraft((value) => ({ ...value, dateFrom: event.target.value }))} /><span>至</span><input aria-label="结束时间" type="datetime-local" value={draft.dateTo} onChange={(event) => setDraft((value) => ({ ...value, dateTo: event.target.value }))} /></div></fieldset>
      <label><span>数据类型</span><select value={draft.category} onChange={(event) => setDraft((value) => ({ ...value, category: event.target.value }))}>{(catalogQuery.data?.categories ?? [{ key: 'location', label: '位置数据' }, { key: 'raw', label: '原始报文' }, { key: 'mileage', label: '日里程' }]).map((item) => <option key={item.key} value={item.key}>{item.label}</option>)}</select></label>
      <label><span>数据来源</span><select value={draft.protocol} onChange={(event) => setDraft((value) => ({ ...value, protocol: event.target.value }))}><option value="">全部来源</option><option value="GB32960">GB32960</option><option value="JT808">JT808</option><option value="YUTONG_MQTT">YUTONG_MQTT</option></select></label>
      <button className="v2-primary-button" type="submit" disabled={!parseHistoryKeywords(draft.keywords).length}>查询</button><button className="v2-secondary-button" type="button" onClick={reset}>重置</button>{exportAllowed ? <CreateExportButton disabled={!result?.rows.length} request={{ keywords, category: criteria.category, protocol: criteria.protocol || undefined, dateFrom: criteria.dateFrom, dateTo: criteria.dateTo, metrics: visibleKeys, format: 'csv' }} /> : <span className="v2-role-badge">只读 · 导出需操作员权限</span>}
    </form>
    <div className="v2-history-metrics"><strong>已选字段</strong>{visibleMetrics.map((metric) => <button type="button" className="is-active" onClick={() => toggleMetric(metric.key)} key={metric.key} title="点击取消显示"><i />{metric.label}{metric.unit ? ` (${metric.unit})` : ''}</button>)}{allMetrics.length > visibleMetrics.length ? <span>另有 {allMetrics.length - visibleMetrics.length} 项可选</span> : !allMetrics.length ? <span>查询后加载可用指标</span> : null}<button type="button" className="v2-history-metrics-manage" onClick={() => setColumnSettingsOpen(true)} disabled={!allMetrics.length}><IconSetting />管理字段</button></div>
    {dataQuery.isError ? <InlineError message={dataQuery.error instanceof Error ? dataQuery.error.message : '历史查询失败'} onRetry={() => dataQuery.refetch()} /> : null}
    <div className="v2-history-workspace">
      <div className="v2-history-main">
        <div className="v2-history-summary"><div><small>结果行数</small><strong>{result?.total.toLocaleString('zh-CN') ?? '—'}</strong></div><div><small>车辆数</small><strong>{result?.summary.vehicleCount ?? '—'}</strong></div><div><small>数据源</small><strong>{result?.summary.sources.join('、') || '—'}</strong></div><div><small>查询耗时</small><strong>{result ? `${result.summary.queryDurationMs} ms` : '—'}</strong></div></div>
        <HistoryTrend response={seriesQuery.data} category={criteria.category} loading={seriesQuery.isFetching} error={seriesQuery.isError ? (seriesQuery.error instanceof Error ? seriesQuery.error.message : '未知错误') : undefined} hasMetrics={Boolean(seriesMetricKey)} />
        <section className={`v2-history-table-card is-${density}`}><header><strong>数据明细</strong><div><button type="button" aria-label="列设置" aria-haspopup="dialog" aria-expanded={columnSettingsOpen} onClick={() => setColumnSettingsOpen((value) => !value)}><IconSetting />列设置</button><select value={density} onChange={(event) => setDensity(event.target.value as typeof density)}><option value="compact">紧凑</option><option value="comfortable">舒适</option></select><button type="button" onClick={() => dataQuery.refetch()} aria-label="刷新历史数据"><IconRefresh /></button></div></header>{columnSettingsOpen ? <ColumnVisibilityPanel metrics={allMetrics} visibleKeys={visibleKeys} onToggle={toggleMetric} onShowAll={() => setVisibleMetrics(allMetrics.map((metric) => metric.key))} onReset={() => setVisibleMetrics(allMetrics.filter((metric) => metric.defaultVisible).map((metric) => metric.key))} onClose={() => setColumnSettingsOpen(false)} /> : null}<div className="v2-history-table-scroll"><table><thead><tr><th aria-label="选择行" /><th>设备时间</th><th>服务时间</th><th>车牌</th><th>VIN</th><th>协议</th>{visibleMetrics.map((metric) => <th key={metric.key}>{metric.label}{metric.unit ? ` (${metric.unit})` : ''}</th>)}<th>质量</th><th>质量原因</th><th>操作</th></tr></thead><tbody>{result?.rows.map((row) => <tr className={selectedRow?.id === row.id ? 'is-selected' : ''} key={row.id}><td><input type="checkbox" checked={selectedRow?.id === row.id} onChange={() => setSelectedRowRef(selectedRow?.id === row.id ? undefined : { scope: dataScope, id: row.id })} aria-label={`选择 ${row.plate || row.vin} ${row.deviceTime}`} /></td><td>{row.deviceTime}</td><td>{row.serverTime}</td><td>{row.plate || '—'}</td><td title={row.vin}>{row.vin}</td><td>{row.protocol}</td>{visibleMetrics.map((metric) => <td key={metric.key}>{formatHistoryValue(row.values[metric.key], metric)}</td>)}<td><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></td><td className="v2-history-quality-reason" title={row.qualityReason}>{row.qualityReason || '—'}</td><td><button type="button" onClick={() => setSelectedRowRef({ scope: dataScope, id: row.id })}>查看详情</button></td></tr>)}</tbody></table><div className="v2-history-mobile-list">{result?.rows.map((row) => <button type="button" key={row.id} className={selectedRow?.id === row.id ? 'is-selected' : ''} onClick={() => setSelectedRowRef({ scope: dataScope, id: row.id })}><header><div><strong>{row.plate || '未绑定车牌'}</strong><span>{row.vin}</span></div><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></header><p><time>{row.deviceTime}</time><b>{row.protocol}</b></p><small className="v2-history-mobile-quality">{row.qualityReason || '平台未返回质量说明'}</small><dl>{visibleMetrics.slice(0, 4).map((metric) => <div key={metric.key}><dt>{metric.label}</dt><dd>{formatHistoryValue(row.values[metric.key], metric)}</dd></div>)}</dl><em>查看完整详情</em></button>)}</div>{dataQuery.isPending && keywords.length ? <div className="v2-history-loading" role="status"><i />正在加载当前筛选范围的历史数据…</div> : !result?.rows.length ? <div className="v2-history-empty">{keywords.length ? '当前条件没有历史记录' : '输入车辆并查询历史数据'}</div> : null}</div><footer><span>第 {page} / {totalPages} 页，共 {result?.total ?? 0} 条</span><div><button type="button" disabled={page <= 1} onClick={() => setOffset(Math.max(0, offset - limit))}>上一页</button><button type="button" disabled={page >= totalPages} onClick={() => setOffset(offset + limit)}>下一页</button><select value={limit} onChange={(event) => { setLimit(Number(event.target.value)); setOffset(0); }}><option value="20">20 条/页</option><option value="50">50 条/页</option><option value="100">100 条/页</option></select></div></footer></section>
      </div>
      <aside className="v2-history-side"><EvidencePanel row={selectedRow} metrics={visibleMetrics} onClose={() => setSelectedRowRef(undefined)} />{exportAllowed ? <ExportJobsPanel /> : null}</aside>
    </div>
  </div>;
}
