import { IconArrowDown, IconArrowUp, IconClose, IconDownload, IconRefresh, IconSearch, IconSetting } from '@douyinfe/semi-icons';
import { useQuery } from '@tanstack/react-query';
import { FormEvent, useEffect, useMemo, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { api } from '../../api/client';
import type { DailyMileageRow, MileageStatistics, Page, VehicleRow } from '../../api/types';
import { createMileageExportStream, type MileageExportStream } from '../domain/mileageExport';
import { formatZhNumber } from '../domain/formatters';
import { InlineError } from '../shared/AsyncState';
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
import { QUERY_MEMORY, retainPreviousPageWithinScope } from '../queryPolicy';
import { monitorReturnFromParams, preserveMonitorReturn } from '../routing/monitorContext';

const DAY = 86_400_000;
const DETAIL_LIMIT = 10_000;
const MAX_SELECTED_VEHICLES = 20;
const PAGE_SIZE = 20;
const EXPORT_VEHICLE_PAGE_SIZE = 2_000;
const EXPORT_VIN_BATCH_SIZE = 50;
const MAX_MILEAGE_RANGE_DAYS = 366;

type VehicleOption = Pick<VehicleRow, 'vin' | 'plate'>;
type MileageProtocol = 'GB32960' | 'JT808' | 'YUTONG_MQTT';
type MileageSourceOption = { protocol: MileageProtocol; label: string; mileageType: string; enabled: boolean };
type Criteria = { vehicles: VehicleOption[]; dateFrom: string; dateTo: string; sources: MileageSourceOption[] };
type ExportProgress = { label: string; completed?: number; total?: number };

const SOURCE_STORAGE_KEY = 'vehicle-platform:mileage-source-strategy';
const DEFAULT_SOURCES: MileageSourceOption[] = [
  { protocol: 'GB32960', label: '国标 GB32960', mileageType: '仪表盘里程', enabled: true },
  { protocol: 'JT808', label: '交通部 JT/T 808', mileageType: 'GPS 里程', enabled: true },
  { protocol: 'YUTONG_MQTT', label: '宇通 MQTT', mileageType: '仪表盘里程', enabled: true }
];

function localDate(value = new Date()) {
  const offset = value.getTimezoneOffset() * 60_000;
  return new Date(value.getTime() - offset).toISOString().slice(0, 10);
}

function defaultWindow(days = 7, endOffsetDays = 0) {
  const end = new Date(Date.now() - endOffsetDays * DAY);
  return { dateFrom: localDate(new Date(end.getTime() - (days - 1) * DAY)), dateTo: localDate(end) };
}

function isSameRange(criteria: Pick<Criteria, 'dateFrom' | 'dateTo'>, range: { dateFrom: string; dateTo: string }) {
  return criteria.dateFrom === range.dateFrom && criteria.dateTo === range.dateTo;
}

function formatKm(value?: number) {
  if (value == null || !Number.isFinite(value)) return '—';
  return formatZhNumber(value, 1);
}

function inclusiveDays(dateFrom: string, dateTo: string) {
  const from = Date.parse(`${dateFrom}T00:00:00Z`);
  const to = Date.parse(`${dateTo}T00:00:00Z`);
  return Number.isFinite(from) && Number.isFinite(to) && to >= from ? Math.round((to - from) / DAY) + 1 : 0;
}

function mileageDateRangeError(criteria: Pick<Criteria, 'dateFrom' | 'dateTo'>) {
  if (!criteria.dateFrom || !criteria.dateTo) return '请选择完整的开始和结束日期';
  const days = inclusiveDays(criteria.dateFrom, criteria.dateTo);
  if (!days) return '结束日期不能早于开始日期';
  if (days > MAX_MILEAGE_RANGE_DAYS) return `单次最多查询 ${MAX_MILEAGE_RANGE_DAYS} 个自然日，请缩小日期范围`;
  return '';
}

function mileageParams(criteria: Criteria, offset = 0) {
  const params = new URLSearchParams({ dateFrom: criteria.dateFrom, dateTo: criteria.dateTo });
  if (criteria.vehicles.length) params.set('vins', criteria.vehicles.map((vehicle) => vehicle.vin).join(','));
  else params.set('vehicleScope', 'bound');
  params.set('protocols', criteria.sources.filter((source) => source.enabled).map((source) => source.protocol).join(','));
  if (offset >= 0) {
    params.set('deduplicate', '1');
    params.set('limit', String(DETAIL_LIMIT));
    params.set('offset', String(offset));
  }
  return params;
}

function initialCriteria(searchParams: URLSearchParams): Criteria {
  const defaults = defaultWindow();
  const vins = (searchParams.get('vins') ?? '').split(',').map((vin) => vin.trim()).filter(Boolean).slice(0, MAX_SELECTED_VEHICLES);
  const requestedProtocols = (searchParams.get('protocols') ?? '').split(',').filter((protocol): protocol is MileageProtocol => DEFAULT_SOURCES.some((source) => source.protocol === protocol));
  let sources = DEFAULT_SOURCES.map((source) => ({ ...source }));
  if (requestedProtocols.length) {
    sources = [...requestedProtocols.map((protocol) => ({ ...DEFAULT_SOURCES.find((source) => source.protocol === protocol)!, enabled: true })), ...DEFAULT_SOURCES.filter((source) => !requestedProtocols.includes(source.protocol)).map((source) => ({ ...source, enabled: false }))];
  } else {
    try {
      const stored = JSON.parse(window.localStorage.getItem(SOURCE_STORAGE_KEY) ?? '[]') as Partial<MileageSourceOption>[];
      const normalized = stored.flatMap((item) => {
        const source = DEFAULT_SOURCES.find((candidate) => candidate.protocol === item.protocol);
        return source ? [{ ...source, enabled: item.enabled !== false }] : [];
      });
      if (normalized.length === DEFAULT_SOURCES.length && normalized.some((source) => source.enabled)) sources = normalized;
    } catch { /* retain safe defaults */ }
  }
  return {
    vehicles: vins.map((vin) => ({ vin, plate: '' })),
    dateFrom: searchParams.get('dateFrom') ?? defaults.dateFrom,
    dateTo: searchParams.get('dateTo') ?? defaults.dateTo,
    sources
  };
}

function SourceStrategy({ value, onChange }: { value: MileageSourceOption[]; onChange: (sources: MileageSourceOption[]) => void }) {
  const [open, setOpen] = useState(false);
  const enabled = value.filter((source) => source.enabled);
  const update = (sources: MileageSourceOption[]) => {
    onChange(sources);
    try { window.localStorage.setItem(SOURCE_STORAGE_KEY, JSON.stringify(sources)); } catch { /* preference persistence is optional */ }
  };
  const move = (index: number, direction: -1 | 1) => {
    const target = index + direction;
    if (target < 0 || target >= value.length) return;
    const next = [...value];
    [next[index], next[target]] = [next[target], next[index]];
    update(next);
  };
  const toggle = (protocol: MileageProtocol) => {
    const current = value.find((source) => source.protocol === protocol);
    if (current?.enabled && enabled.length === 1) return;
    update(value.map((source) => source.protocol === protocol ? { ...source, enabled: !source.enabled } : source));
  };

  return <div className="v2-mileage-source-strategy">
    <button type="button" className="v2-mileage-source-trigger" aria-haspopup="dialog" aria-expanded={open} onClick={() => setOpen((current) => !current)}>
      <IconSetting /><span>数据源</span><b>{enabled.length}/3</b>
    </button>
    {open ? <section className="v2-mileage-source-popover" role="dialog" aria-label="数据源策略配置">
      <header><div><strong>数据源策略</strong><span>同车同日按优先级选择一个里程来源</span></div><button type="button" aria-label="关闭数据源策略" onClick={() => setOpen(false)}><IconClose /></button></header>
      <div className="v2-mileage-source-list">{value.map((source, index) => <article key={source.protocol} className={source.enabled ? '' : 'is-disabled'}>
        <button type="button" className={`v2-mileage-source-switch${source.enabled ? ' is-on' : ''}`} role="switch" aria-checked={source.enabled} aria-label={`${source.enabled ? '禁用' : '启用'} ${source.label}`} onClick={() => toggle(source.protocol)}><i /></button>
        <div><strong>{source.label}</strong><small><b>{source.mileageType}</b><code>{source.protocol}</code></small></div>
        <em>{source.enabled ? `优先级 ${enabled.findIndex((item) => item.protocol === source.protocol) + 1}` : '已禁用'}</em>
        <p><button type="button" aria-label={`上移 ${source.label}`} disabled={index === 0} onClick={() => move(index, -1)}><IconArrowUp /></button><button type="button" aria-label={`下移 ${source.label}`} disabled={index === value.length - 1} onClick={() => move(index, 1)}><IconArrowDown /></button></p>
      </article>)}</div>
      <footer><span>同车同日只使用一个来源，避免重复累计；至少保留一个来源。</span><button type="button" onClick={() => setOpen(false)}>完成</button></footer>
    </section> : null}
  </div>;
}

function VehicleMultiSelect({ value, onChange }: { value: VehicleOption[]; onChange: (vehicles: VehicleOption[]) => void }) {
  const [search, setSearch] = useState('');
  const [debounced, setDebounced] = useState('');
  const [open, setOpen] = useState(false);
  const closeTimerRef = useRef<number>();
  useEffect(() => { const timer = window.setTimeout(() => setDebounced(search.trim()), 220); return () => window.clearTimeout(timer); }, [search]);
  useEffect(() => () => window.clearTimeout(closeTimerRef.current), []);
  const openPicker = () => {
    window.clearTimeout(closeTimerRef.current);
    setOpen(true);
  };
  const closePicker = () => {
    window.clearTimeout(closeTimerRef.current);
    closeTimerRef.current = window.setTimeout(() => setOpen(false), 120);
  };
  const candidateParams = useMemo(() => {
    const params = new URLSearchParams({ limit: '12', offset: '0' });
    if (debounced) params.set('keyword', debounced);
    return params;
  }, [debounced]);
  const candidates = useQuery({
    queryKey: ['mileage-vehicle-options', candidateParams.toString()],
    queryFn: ({ signal }) => api.vehicles(candidateParams, signal),
    enabled: open,
    staleTime: 30_000,
    gcTime: QUERY_MEMORY.optionGcTime
  });
  const selected = useMemo(() => new Set(value.map((vehicle) => vehicle.vin)), [value]);
  const options = (candidates.data?.items ?? []).filter((vehicle, index, rows) => rows.findIndex((item) => item.vin === vehicle.vin) === index);

  const add = (vehicle: VehicleRow) => {
    if (selected.has(vehicle.vin) || value.length >= MAX_SELECTED_VEHICLES) return;
    onChange([...value, { vin: vehicle.vin, plate: vehicle.plate }]);
    setSearch('');
  };

  return <label className="v2-mileage-vehicle-field">
    <span>车牌</span>
    <div className={`v2-mileage-multiselect${open ? ' is-open' : ''}`}>
      <IconSearch />
      <div className="v2-mileage-selection">
        {value.map((vehicle) => <button key={vehicle.vin} type="button" className="v2-mileage-chip" title={vehicle.vin} onClick={() => onChange(value.filter((item) => item.vin !== vehicle.vin))}>
          <span>{vehicle.plate || vehicle.vin}</span><IconClose />
        </button>)}
        <input value={search} onFocus={openPicker} onBlur={closePicker} onChange={(event) => { setSearch(event.target.value); setOpen(true); }} placeholder={value.length ? '继续添加车牌' : '输入车牌搜索，可多选'} aria-label="搜索车牌" />
      </div>
      {open ? <div className="v2-mileage-options" role="listbox">
        <header><span>车牌候选</span><em>{value.length}/{MAX_SELECTED_VEHICLES} 已选</em></header>
        {candidates.isLoading ? <p>正在搜索车辆…</p> : null}
        {!candidates.isLoading && candidates.isError ? <div className="v2-vehicle-option-error" role="alert"><span>{candidates.error instanceof Error ? candidates.error.message : '车牌候选加载失败'}</span><button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => void candidates.refetch()}>重试</button></div> : null}
        {!candidates.isLoading && options.map((vehicle) => <button type="button" role="option" aria-selected={selected.has(vehicle.vin)} key={vehicle.vin} disabled={selected.has(vehicle.vin)} onMouseDown={(event) => event.preventDefault()} onClick={() => add(vehicle)}>
          <strong>{vehicle.plate || '未绑定车牌'}</strong><span>{vehicle.vin}</span>{selected.has(vehicle.vin) ? <em>已选择</em> : null}
        </button>)}
        {!candidates.isLoading && !candidates.isError && !options.length ? <p>没有匹配的车牌</p> : null}
      </div> : null}
    </div>
    <small>支持车牌关键字搜索，最多选择 {MAX_SELECTED_VEHICLES} 辆</small>
  </label>;
}

function SummaryRail({ data, criteria, fleetTotal, loading }: { data?: MileageStatistics; criteria: Criteria; fleetTotal?: number; loading: boolean }) {
  const days = inclusiveDays(criteria.dateFrom, criteria.dateTo);
  const vehicleCount = criteria.vehicles.length ? data?.vehicleCount ?? 0 : fleetTotal ?? 0;
  const items = loading ? [
    ['已绑定主车辆', '—', '正在按新筛选范围查询'],
    ['统计天数', `${days} 天`, `${criteria.dateFrom} 至 ${criteria.dateTo}`],
    ['区间总里程', '—', '正在计算区间汇总'],
    ['日均里程', '—', '正在计算有效车辆日']
  ] : [
    ['已绑定主车辆', `${vehicleCount} 辆`, criteria.vehicles.length ? `已选择 ${criteria.vehicles.length} 辆` : `档案口径 · ${data?.vehicleCount ?? 0} 辆有里程`],
    ['统计天数', `${days} 天`, `${criteria.dateFrom} 至 ${criteria.dateTo}`],
    ['区间总里程', `${formatKm(data?.periodMileageKm)} km`, `${data?.recordCount ?? 0} 条车辆日记录`],
    ['日均里程', `${formatKm(data?.averageDailyMileageKm)} km`, '按有效车辆日平均']
  ];
  const primary = items[2];
  const secondary = [items[0], items[1], items[3]];
  return <section className="v2-mileage-summary" aria-label="里程查询统计信息">
    <article className="is-primary"><small>{primary[0]}</small><strong>{primary[1]}</strong><span>{primary[2]}</span></article>
    <div className="v2-mileage-summary-secondary">{secondary.map(([label, value, note]) => <article key={label}><small>{label}</small><strong>{value}</strong><span>{note}</span></article>)}</div>
  </section>;
}

type VehicleMileageMatrix = VehicleOption & { days: Map<string, number>; sources: Map<string, string>; totalMileageKm: number };

function rangeDates(dateFrom: string, dateTo: string) {
  const dates: string[] = [];
  const cursor = new Date(`${dateFrom}T00:00:00Z`);
  const end = new Date(`${dateTo}T00:00:00Z`);
  while (cursor <= end && dates.length < MAX_MILEAGE_RANGE_DAYS) {
    dates.push(cursor.toISOString().slice(0, 10));
    cursor.setUTCDate(cursor.getUTCDate() + 1);
  }
  return dates;
}

function dateLabel(date: string) {
  const [, month, day] = date.split('-');
  return `${Number(month)}/${Number(day)}`;
}

function MileageTable({ rows, dates }: { rows: VehicleMileageMatrix[]; dates: string[] }) {
  let maxDailyMileage = 1;
  for (const row of rows) {
    for (const mileage of row.days.values()) maxDailyMileage = Math.max(maxDailyMileage, mileage);
  }
  return <div className="v2-mileage-table-wrap">
      <table className="v2-mileage-table">
        <thead><tr><th className="is-sticky is-plate">车牌</th><th className="is-sticky is-vin">VIN</th>{dates.map((date) => <th key={date} className="is-number is-date" title={date}>{dateLabel(date)}</th>)}<th className="is-number is-total">区间总里程</th></tr></thead>
        <tbody>{rows.map((row) => <tr key={row.vin}><td className="is-sticky is-plate"><strong>{row.plate || '未绑定'}</strong></td><td className="is-sticky is-vin"><code>{row.vin}</code></td>{dates.map((date) => {
          const mileage = row.days.get(date);
          const intensity = mileage && mileage > 0 ? .035 + mileage / maxDailyMileage * .13 : 0;
          return <td key={date} className={`is-number${mileage != null ? ' is-daily' : ' is-empty'}`} title={mileage != null ? `来源：${row.sources.get(date) || '—'}` : undefined} style={intensity ? { backgroundColor: `rgba(37, 99, 235, ${intensity.toFixed(3)})` } : undefined}>{mileage != null ? `${formatKm(mileage)} km` : '—'}</td>;
        })}<td className="is-number is-period is-total">{formatKm(row.totalMileageKm)} km</td></tr>)}</tbody>
      </table>
    </div>;
}

export default function StatisticsPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const monitorReturn = monitorReturnFromParams(searchParams);
  const [draft, setDraft] = useState<Criteria>(() => initialCriteria(searchParams));
  const [criteria, setCriteria] = useState<Criteria>(() => initialCriteria(searchParams));
  const [page, setPage] = useState(1);
  const [isExporting, setIsExporting] = useState(false);
  const [exportFeedback, setExportFeedback] = useState('');
  const [exportProgress, setExportProgress] = useState<ExportProgress>();
  const [validationError, setValidationError] = useState('');
  const [filtersCollapsed, setFiltersCollapsed] = useState(true);
  const exportControllerRef = useRef<AbortController | null>(null);
  const mountedRef = useRef(true);
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      exportControllerRef.current?.abort();
    };
  }, []);
  const hasVehicles = criteria.vehicles.length > 0;
  const criteriaError = mileageDateRangeError(criteria);
  const fleetParams = useMemo(() => new URLSearchParams({ limit: String(PAGE_SIZE), offset: String((page - 1) * PAGE_SIZE), bindingStatus: 'bound' }), [page]);
  const fleetVehicles = useQuery({
    queryKey: ['mileage-fleet-page', fleetParams.toString()],
    queryFn: ({ signal }) => api.vehicleCoverage(fleetParams, signal),
    enabled: !hasVehicles,
    staleTime: 60_000,
    gcTime: QUERY_MEMORY.summaryGcTime
  });
  const displayVehicles = useMemo<VehicleOption[]>(() => hasVehicles
    ? criteria.vehicles
    : (fleetVehicles.data?.items ?? []).map((vehicle) => ({ vin: vehicle.vin, plate: vehicle.plate })), [criteria.vehicles, fleetVehicles.data?.items, hasVehicles]);
  const statisticsParams = useMemo(() => mileageParams(criteria, -1), [criteria]);
  const statisticsScope = statisticsParams.toString();
  const rowsCriteria = useMemo(() => ({ ...criteria, vehicles: displayVehicles }), [criteria, displayVehicles]);
  const rowsParams = useMemo(() => mileageParams(rowsCriteria, 0), [rowsCriteria]);
  const statistics = useQuery({ queryKey: ['mileage-statistics', statisticsScope], queryFn: ({ signal }) => api.mileageStatistics(statisticsParams, signal), enabled: !criteriaError, staleTime: 60_000, gcTime: QUERY_MEMORY.summaryGcTime });
  const mileage = useQuery<Page<DailyMileageRow>>({ queryKey: ['daily-mileage-query', statisticsScope, rowsParams.toString()], queryFn: ({ signal }) => api.dailyMileage(rowsParams, signal), enabled: !criteriaError && displayVehicles.length > 0, staleTime: 60_000, gcTime: QUERY_MEMORY.highVolumeGcTime, placeholderData: retainPreviousPageWithinScope<Page<DailyMileageRow>>(statisticsScope, 2) });
  const dates = useMemo(() => criteriaError ? [] : rangeDates(criteria.dateFrom, criteria.dateTo), [criteria.dateFrom, criteria.dateTo, criteriaError]);
  const mileageByVin = useMemo(() => {
    const index = new Map<string, { plate: string; days: Map<string, number>; sources: Map<string, string> }>();
    for (const row of mileage.data?.items ?? []) {
      let entry = index.get(row.vin);
      if (!entry) {
        entry = { plate: row.plate || '', days: new Map(), sources: new Map() };
        index.set(row.vin, entry);
      } else if (!entry.plate && row.plate) entry.plate = row.plate;
      entry.days.set(row.date, row.dailyMileageKm);
      entry.sources.set(row.date, row.source);
    }
    return index;
  }, [mileage.data?.items]);
  const rankingByVin = useMemo(() => new Map((statistics.data?.ranking ?? []).map((row) => [row.vin, row])), [statistics.data?.ranking]);
  const matrixRows = useMemo(() => displayVehicles.map((vehicle) => {
    const daily = mileageByVin.get(vehicle.vin);
    const ranking = rankingByVin.get(vehicle.vin);
    const days = daily?.days ?? new Map<string, number>();
    const sources = daily?.sources ?? new Map<string, string>();
    let dailyTotal = 0;
    for (const value of days.values()) dailyTotal += value;
    return { ...vehicle, plate: vehicle.plate || daily?.plate || ranking?.plate || '', days, sources, totalMileageKm: ranking?.mileageKm ?? dailyTotal };
  }), [displayVehicles, mileageByVin, rankingByVin]);
  const totalVehicles = hasVehicles ? criteria.vehicles.length : fleetVehicles.data?.total ?? 0;
  const totalPages = Math.max(1, Math.ceil(totalVehicles / PAGE_SIZE));
  const submit = (event: FormEvent) => {
    event.preventDefault();
    const error = mileageDateRangeError(draft);
    setValidationError(error);
    if (error) return;
    setPage(1); setExportFeedback(''); setCriteria(draft); setSearchParams(preserveMonitorReturn(mileageParams(draft, -1), monitorReturn), { replace: true }); setFiltersCollapsed(true);
  };
  const setRange = (range: { dateFrom: string; dateTo: string }) => { const next = { ...draft, ...range }; setPage(1); setExportFeedback(''); setValidationError(''); setDraft(next); setCriteria(next); setSearchParams(preserveMonitorReturn(mileageParams(next, -1), monitorReturn), { replace: true }); };
  const todayRange = defaultWindow(1);
  const yesterdayRange = defaultWindow(1, 1);
  const sevenDayRange = defaultWindow(7);
  const thirtyDayRange = defaultWindow(30);
  const ninetyDayRange = defaultWindow(90);
  const refreshing = statistics.isFetching || mileage.isFetching || fleetVehicles.isFetching;
  const resultsLoading = fleetVehicles.isLoading || mileage.isLoading || mileage.isPlaceholderData;
  const exportPercent = exportProgress?.total
    ? Math.min(100, Math.round((exportProgress.completed ?? 0) / exportProgress.total * 100))
    : undefined;

  const exportExcel = async () => {
    if (exportControllerRef.current || !totalVehicles || criteriaError) return;
    const controller = new AbortController();
    exportControllerRef.current = controller;
    const reportProgress = (progress: ExportProgress) => {
      if (mountedRef.current && !controller.signal.aborted) setExportProgress(progress);
    };
    setIsExporting(true);
    setExportFeedback('');
    let exportStream: MileageExportStream | undefined;
    try {
      let vehicles: VehicleOption[] = criteria.vehicles.map((vehicle) => ({ ...vehicle }));
      if (!vehicles.length) {
        vehicles = [];
        let offset = 0;
        reportProgress({ label: '正在读取车辆档案', completed: 0, total: totalVehicles });
        while (offset < totalVehicles) {
          const result = await api.vehicleCoverage(new URLSearchParams({ limit: String(EXPORT_VEHICLE_PAGE_SIZE), offset: String(offset), bindingStatus: 'bound' }), controller.signal);
          vehicles.push(...result.items.map((vehicle) => ({ vin: vehicle.vin, plate: vehicle.plate })));
          reportProgress({ label: '正在读取车辆档案', completed: vehicles.length, total: result.total });
          if (!result.items.length) break;
          offset += result.items.length;
          if (offset >= result.total) break;
        }
      }
      const plateByVin = new Map(vehicles.filter((vehicle) => vehicle.plate).map((vehicle) => [vehicle.vin, vehicle.plate]));
      exportStream = createMileageExportStream({
        dateFrom: criteria.dateFrom,
        dateTo: criteria.dateTo,
        dates,
        sources: criteria.sources.filter((source) => source.enabled),
        exportedAt: new Date()
      }, controller.signal);
      const vehicleBatches = criteria.vehicles.length
        ? Array.from({ length: Math.ceil(vehicles.length / EXPORT_VIN_BATCH_SIZE) }, (_, index) => vehicles.slice(index * EXPORT_VIN_BATCH_SIZE, (index + 1) * EXPORT_VIN_BATCH_SIZE))
        : [[] as VehicleOption[]];
      for (const [batchIndex, vehicleBatch] of vehicleBatches.entries()) {
        let offset = 0;
        const mileageLabel = vehicleBatches.length > 1
          ? `正在读取里程数据（${batchIndex + 1}/${vehicleBatches.length} 批）`
          : '正在读取全部车辆里程';
        reportProgress({ label: mileageLabel });
        while (true) {
          const params = mileageParams({ ...criteria, vehicles: vehicleBatch }, offset);
          const result = await api.dailyMileage(params, controller.signal);
          for (const row of result.items) if (row.plate) plateByVin.set(row.vin, row.plate);
          exportStream.appendRows(result.items);
          offset += result.items.length;
          reportProgress({ label: mileageLabel, completed: offset, total: result.total });
          if (!result.items.length || offset >= result.total) break;
        }
      }
      vehicles = vehicles.map((vehicle) => ({ ...vehicle, plate: vehicle.plate || plateByVin.get(vehicle.vin) || '' }));
      reportProgress({ label: '正在后台生成 Excel 文件' });
      await exportStream.finish(vehicles);
      exportStream = undefined;
      if (mountedRef.current) setExportFeedback(`已导出 ${vehicles.length} 辆车`);
    } catch (error) {
      if (mountedRef.current) {
        setExportFeedback(controller.signal.aborted
          ? '已取消导出'
          : error instanceof Error ? `导出失败：${error.message}` : '导出失败，请稍后重试');
      }
    } finally {
      exportStream?.dispose();
      if (exportControllerRef.current === controller) exportControllerRef.current = null;
      if (mountedRef.current) {
        setIsExporting(false);
        setExportProgress(undefined);
      }
    }
  };

  const cancelExport = () => {
    if (!exportControllerRef.current || exportControllerRef.current.signal.aborted) return;
    setExportProgress({ label: '正在取消导出' });
    exportControllerRef.current.abort();
  };

  return <div className="v2-mileage-page">
    <MonitorReturnBar />
    <section className="v2-mileage-query-panel">
      <button type="button" className="v2-mobile-filter-toggle" aria-expanded={!filtersCollapsed} onClick={() => setFiltersCollapsed((value) => !value)}><span><b>查询条件</b><small>{criteria.vehicles.length ? `已选 ${criteria.vehicles.length} 辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天` : `全部车辆 · ${inclusiveDays(criteria.dateFrom, criteria.dateTo)} 天`}</small></span><em>{filtersCollapsed ? '修改' : '收起'}</em></button>
      <form className={`v2-mileage-filter${filtersCollapsed ? ' is-mobile-collapsed' : ''}`} onSubmit={submit}>
        <VehicleMultiSelect value={draft.vehicles} onChange={(vehicles) => setDraft((current) => ({ ...current, vehicles }))} />
        <label><span>开始日期</span><input type="date" value={draft.dateFrom} max={draft.dateTo} onChange={(event) => setDraft((current) => ({ ...current, dateFrom: event.target.value }))} /></label>
        <label><span>结束日期</span><input type="date" value={draft.dateTo} min={draft.dateFrom} onChange={(event) => setDraft((current) => ({ ...current, dateTo: event.target.value }))} /></label>
        <button className="v2-primary-button" type="submit">查询</button>
        <SourceStrategy value={draft.sources} onChange={(sources) => setDraft((current) => ({ ...current, sources }))} />
        <div className="v2-mileage-ranges"><span>快捷范围</span><button className={isSameRange(draft, todayRange) ? 'is-active' : ''} type="button" onClick={() => setRange(todayRange)}>今天</button><button className={isSameRange(draft, yesterdayRange) ? 'is-active' : ''} type="button" onClick={() => setRange(yesterdayRange)}>昨天</button><button className={isSameRange(draft, sevenDayRange) ? 'is-active' : ''} type="button" onClick={() => setRange(sevenDayRange)}>近 7 天</button><button className={isSameRange(draft, thirtyDayRange) ? 'is-active' : ''} type="button" onClick={() => setRange(thirtyDayRange)}>近 30 天</button><button className={isSameRange(draft, ninetyDayRange) ? 'is-active' : ''} type="button" onClick={() => setRange(ninetyDayRange)}>近 90 天</button></div>
      </form>
      {validationError || criteriaError ? <p className="v2-mileage-validation" role="alert">{validationError || criteriaError}</p> : null}
      <SummaryRail data={statistics.data} criteria={criteria} fleetTotal={fleetVehicles.data?.total} loading={statistics.isLoading} />
    </section>
    {statistics.isError || mileage.isError || fleetVehicles.isError ? <InlineError message={(statistics.error ?? mileage.error ?? fleetVehicles.error) instanceof Error ? (statistics.error ?? mileage.error ?? fleetVehicles.error as Error).message : '里程数据加载失败'} onRetry={() => { statistics.refetch(); mileage.refetch(); if (!hasVehicles) fleetVehicles.refetch(); }} /> : null}
    <section className="v2-mileage-results">
      <header><div><strong>车辆每日里程</strong><span>{criteria.dateFrom} 至 {criteria.dateTo}</span></div><div className="v2-mileage-result-actions"><em>{hasVehicles ? `${totalVehicles} 辆车` : `当前 ${displayVehicles.length} 辆 / 共 ${totalVehicles} 辆`} · {dates.length} 个自然日</em><button className="is-refresh" type="button" aria-label="刷新里程数据" onClick={() => { statistics.refetch(); mileage.refetch(); if (!hasVehicles) fleetVehicles.refetch(); }} disabled={refreshing}><IconRefresh />{refreshing ? '更新中' : '刷新'}</button><button type="button" aria-label={isExporting ? '取消导出' : '导出 Excel'} onClick={isExporting ? cancelExport : exportExcel} disabled={!totalVehicles || Boolean(criteriaError)}>{isExporting ? <IconClose /> : <IconDownload />}{isExporting ? '取消导出' : '导出 Excel'}</button></div></header>
      {exportProgress ? <div className="v2-mileage-export-progress" role="progressbar" aria-label={exportProgress.label} aria-valuemin={0} aria-valuemax={100} aria-valuenow={exportPercent}>
        <span><strong>{exportProgress.label}</strong><small>{exportPercent == null ? '处理中' : `${exportPercent}%`}</small></span>
        <i className={exportPercent == null ? 'is-indeterminate' : ''}><b style={exportPercent == null ? undefined : { width: `${exportPercent}%` }} /></i>
      </div> : null}
      {resultsLoading ? <div className="v2-mileage-loading" role="status" aria-live="polite"><span className="v2-spinner" /><div><strong>正在查询里程</strong><small>新筛选范围返回前不会展示上一范围的数据</small></div></div> : <MileageTable rows={matrixRows} dates={dates} />}
      {!resultsLoading && !displayVehicles.length ? <div className="v2-mileage-empty">当前没有可展示的车辆</div> : null}
      <footer><span>{hasVehicles ? `已选择 ${totalVehicles} 辆车辆` : `第 ${page} / ${totalPages} 页 · 共 ${totalVehicles} 辆 · 每页 ${PAGE_SIZE} 辆`}{exportFeedback ? ` · ${exportFeedback}` : ''}</span>{!hasVehicles && totalVehicles ? <div><button type="button" disabled={page <= 1 || fleetVehicles.isFetching} onClick={() => setPage((current) => Math.max(1, current - 1))}>上一页</button><button type="button" disabled={page >= totalPages || fleetVehicles.isFetching} onClick={() => setPage((current) => Math.min(totalPages, current + 1))}>下一页</button></div> : null}</footer>
    </section>
    <footer className="v2-mileage-evidence"><span>数据更新时间：{statistics.data?.asOf || '—'}</span><span>来源优先级：{criteria.sources.filter((source) => source.enabled).map((source) => source.protocol).join(' ＞ ')}</span><span>当前筛选复用 1 分钟 · 离开后释放明细</span></footer>
  </div>;
}
