import { Button, Card, Form, Select, SideSheet, Space, Table, Tabs, Tag, Toast, Typography } from '@douyinfe/semi-ui';
import { IconCopy, IconRefresh, IconSearch } from '@douyinfe/semi-icons';
import { useEffect, useMemo, useState } from 'react';
import { api, type RawFrameQuery } from '../api/client';
import type { HistoryLocationRow, MapReverseGeocode, Page, RawFrameRow } from '../api/types';
import { VehicleMap, type VehicleMapPoint } from '../components/VehicleMap';
import { PageHeader } from '../components/PageHeader';
import { isAMapConfigured } from '../config/appConfig';
import { buildAppHash } from '../domain/appRoute';
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';

type HistoryFilters = {
  keyword?: string;
  protocol?: string;
  dateFrom?: string;
  dateTo?: string;
  fields?: string;
  includeFields?: boolean | string;
};

type RawFieldRow = {
  id: string;
  rawId: string;
  vin: string;
  plate: string;
  protocol: string;
  frameType: string;
  deviceTime: string;
  serverTime: string;
  fieldPath: string;
  fieldValue: unknown;
};

const defaultFilters: HistoryFilters = {
  includeFields: false
};

const defaultPage = { items: [], total: 0, limit: 10, offset: 0 };

function canOpenVehicle(vin?: string) {
  const value = vin?.trim();
  return Boolean(value && value !== 'unknown');
}

function dateOnly(value?: string) {
  const match = String(value ?? '').match(/^(\d{4})-(\d{2})-(\d{2})/);
  return match ? `${match[1]}-${match[2]}-${match[3]}` : '';
}

function nextDate(value: string) {
  const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!match) return '';
  const date = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]) + 1));
  return date.toISOString().slice(0, 10);
}

function todayDateString() {
  const date = new Date();
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function previousDateString(value: string) {
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
  if (!match) return value;
  const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]) - 1);
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function splitFields(value?: string) {
  return (value ?? '')
    .split(/[\n,]/)
    .map((item) => item.trim())
    .filter(Boolean);
}

type HistoryTabKey = 'location' | 'raw' | 'fields';

function normalizeHistoryTab(value?: string): HistoryTabKey {
  if (value === 'fields') return 'fields';
  return value === 'raw' ? 'raw' : 'location';
}

function isIncludeFieldsEnabled(value?: boolean | string) {
  return value === true || value === 'true';
}

function hasRawFieldQueryScope(filters: HistoryFilters) {
  return Boolean(
    filters.keyword?.trim() ||
    filters.dateFrom?.trim() ||
    filters.dateTo?.trim() ||
    splitFields(filters.fields).length > 0
  );
}

function shouldBlockRawFieldQuery(filters: HistoryFilters) {
  return isIncludeFieldsEnabled(filters.includeFields) && !hasRawFieldQueryScope(filters);
}

function isFiniteNumber(value: unknown): value is number {
  return typeof value === 'number' && Number.isFinite(value);
}

function hasValidCoordinate(row: HistoryLocationRow) {
  return isFiniteNumber(row.longitude) && isFiniteNumber(row.latitude) && row.longitude !== 0 && row.latitude !== 0;
}

function formatNumber(value?: number, suffix = '') {
  if (!isFiniteNumber(value)) return '-';
  return `${value.toLocaleString(undefined, { maximumFractionDigits: 1 })}${suffix}`;
}

function formatPercent(value?: number) {
  if (!isFiniteNumber(value)) return '-';
  return `${value.toLocaleString(undefined, { maximumFractionDigits: 1 })}%`;
}

function timeValue(row?: HistoryLocationRow) {
  const value = row?.deviceTime || row?.serverTime || row?.lastSeen;
  const ms = Date.parse(String(value ?? '').replace(' ', 'T'));
  return Number.isFinite(ms) ? ms : undefined;
}

function playbackPointId(row: HistoryLocationRow, index: number) {
  return `${row.vin || 'unknown'}-${row.deviceTime || row.serverTime || row.lastSeen || index}-${index}`;
}

function formatDurationMinutes(value?: number, suffix = '') {
  if (!isFiniteNumber(value)) return '-';
  if (value < 1) return `${Math.round(value * 60)} 秒${suffix}`;
  return `${value.toLocaleString(undefined, { maximumFractionDigits: 1 })} 分钟${suffix}`;
}

type TrajectoryAnomalySummary = {
  gapCount: number;
  maxGapMinutes?: number;
  mileageRollbackCount: number;
  maxMileageRollbackKm?: number;
  overspeedCount: number;
  maxSpeedKmh?: number;
};

function analyzeTrajectoryAnomalies(rows: HistoryLocationRow[]): TrajectoryAnomalySummary {
  const summary: TrajectoryAnomalySummary = {
    gapCount: 0,
    mileageRollbackCount: 0,
    overspeedCount: 0
  };
  let previousTime: number | undefined;
  let previousMileage: number | undefined;
  for (const row of rows) {
    const currentTime = timeValue(row);
    if (currentTime != null && previousTime != null) {
      const gapMinutes = Math.abs(currentTime - previousTime) / 60000;
      if (gapMinutes > 30) {
        summary.gapCount += 1;
        summary.maxGapMinutes = summary.maxGapMinutes == null ? gapMinutes : Math.max(summary.maxGapMinutes, gapMinutes);
      }
    }
    if (currentTime != null) {
      previousTime = currentTime;
    }

    if (isFiniteNumber(row.totalMileageKm) && previousMileage != null && row.totalMileageKm < previousMileage) {
      const rollback = previousMileage - row.totalMileageKm;
      summary.mileageRollbackCount += 1;
      summary.maxMileageRollbackKm = summary.maxMileageRollbackKm == null ? rollback : Math.max(summary.maxMileageRollbackKm, rollback);
    }
    if (isFiniteNumber(row.totalMileageKm)) {
      previousMileage = row.totalMileageKm;
    }

    if (isFiniteNumber(row.speedKmh) && row.speedKmh > 120) {
      summary.overspeedCount += 1;
      summary.maxSpeedKmh = summary.maxSpeedKmh == null ? row.speedKmh : Math.max(summary.maxSpeedKmh, row.speedKmh);
    }
  }
  return summary;
}

function mergeInitialFilters(initialVin: string, initialProtocol?: string, initialFilters: Record<string, string> = {}): HistoryFilters {
  const hasExplicitScope = Boolean(
    initialVin?.trim() ||
    initialProtocol?.trim() ||
    initialFilters.keyword?.trim() ||
    initialFilters.protocol?.trim() ||
    initialFilters.dateFrom?.trim() ||
    initialFilters.dateTo?.trim() ||
    initialFilters.fields?.trim()
  );
  const today = todayDateString();
  const defaultDateRange = hasExplicitScope ? {} : {
    dateFrom: previousDateString(today),
    dateTo: today
  };
  return {
    ...defaultFilters,
    ...defaultDateRange,
    keyword: initialVin || '',
    protocol: initialProtocol,
    ...initialFilters,
    includeFields: isIncludeFieldsEnabled(initialFilters.includeFields)
  };
}

const locationExportColumns: CsvColumn<HistoryLocationRow>[] = [
  { title: 'VIN', value: (row) => row.vin },
  { title: '车牌', value: (row) => row.plate },
  { title: '数据通道', value: (row) => row.protocol },
  { title: '经度', value: (row) => row.longitude },
  { title: '纬度', value: (row) => row.latitude },
  { title: '速度km/h', value: (row) => row.speedKmh },
  { title: '总里程km', value: (row) => row.totalMileageKm },
  { title: '设备时间', value: (row) => row.deviceTime },
  { title: '入库时间', value: (row) => row.serverTime }
];

const rawExportColumns: CsvColumn<RawFrameRow>[] = [
  { title: 'ID', value: (row) => row.id },
  { title: 'VIN', value: (row) => row.vin },
  { title: '车牌', value: (row) => row.plate },
  { title: '数据通道', value: (row) => row.protocol },
  { title: '帧类型', value: (row) => row.frameType },
  { title: '大小B', value: (row) => row.rawSizeBytes },
  { title: '设备时间', value: (row) => row.deviceTime },
  { title: '入库时间', value: (row) => row.serverTime },
  { title: '字段明细', value: (row) => row.parsedFields ?? {} }
];

const rawFieldExportColumns: CsvColumn<RawFieldRow>[] = [
  { title: '历史明细ID', value: (row) => row.rawId },
  { title: 'VIN', value: (row) => row.vin },
  { title: '车牌', value: (row) => row.plate },
  { title: '数据通道', value: (row) => row.protocol },
  { title: '帧类型', value: (row) => row.frameType },
  { title: '字段', value: (row) => row.fieldPath },
  { title: '值', value: (row) => formatFieldValue(row.fieldValue) },
  { title: '设备时间', value: (row) => row.deviceTime },
  { title: '入库时间', value: (row) => row.serverTime }
];

function formatFieldValue(value: unknown) {
  if (value == null) return '';
  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
    return String(value);
  }
  return JSON.stringify(value);
}

function flattenParsedFields(value: unknown, prefix = ''): Array<{ path: string; value: unknown }> {
  if (value == null || typeof value !== 'object') {
    return prefix ? [{ path: prefix, value }] : [];
  }
  if (Array.isArray(value)) {
    return value.flatMap((item, index) => flattenParsedFields(item, prefix ? `${prefix}.${index}` : String(index)));
  }
  return Object.entries(value as Record<string, unknown>).flatMap(([key, item]) => {
    const nextPath = prefix ? `${prefix}.${key}` : key;
    if (item != null && typeof item === 'object') {
      return flattenParsedFields(item, nextPath);
    }
    return [{ path: nextPath, value: item }];
  });
}

function exportFileName(prefix: string, filters: HistoryFilters) {
  const keyword = filters.keyword?.trim() || 'all';
  const protocol = filters.protocol?.trim() || 'all-source';
  return `${prefix}-${keyword}-${protocol}.csv`;
}

function trajectorySummaryText({
  filters,
  totalPoints,
  validPointCount,
  mileageDelta,
  maxSpeed,
  firstLocation,
  lastLocation
}: {
  filters: HistoryFilters;
  totalPoints: number;
  validPointCount: number;
  mileageDelta?: number;
  maxSpeed?: number;
  firstLocation?: HistoryLocationRow;
  lastLocation?: HistoryLocationRow;
}) {
  const vehicle = filters.keyword?.trim() || '全部车辆';
  const protocol = filters.protocol?.trim() || '全部数据通道';
  const dateFrom = filters.dateFrom?.trim();
  const dateTo = filters.dateTo?.trim();
  const range = dateFrom || dateTo ? `${dateFrom || '-'} 至 ${dateTo || '-'}` : '全部时间';
  const startTime = firstLocation?.deviceTime || firstLocation?.serverTime || '-';
  const endTime = lastLocation?.deviceTime || lastLocation?.serverTime || '-';
  return [
    '【轨迹回放摘要】',
    `车辆：${vehicle}`,
    `数据通道：${protocol}`,
    `查询范围：${range}`,
    `轨迹点：${totalPoints.toLocaleString()}，有效定位：${validPointCount.toLocaleString()}`,
    `区间里程：${formatNumber(mileageDelta, ' km')}`,
    `最高速度：${formatNumber(maxSpeed, ' km/h')}`,
    `起点：${startTime}`,
    `终点：${endTime}`,
    `当前位置服务：${window.location.origin}${window.location.pathname}${window.location.hash}`
  ].join('\n');
}

function appURL(hash: string) {
  return `${window.location.origin}${window.location.pathname}${hash}`;
}

function historyEvidencePackageText({
  filters,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  mileageDelta,
  maxSpeed,
  firstLocation,
  lastLocation,
  anomalySummary
}: {
  filters: HistoryFilters;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  mileageDelta?: number;
  maxSpeed?: number;
  firstLocation?: HistoryLocationRow;
  lastLocation?: HistoryLocationRow;
  anomalySummary: TrajectoryAnomalySummary;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const evidenceFilters = {
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {})
  };
  return [
    '【数据复核包】',
    `车辆：${vehicle || '全部车辆'}`,
    `数据通道：${protocol || '全部数据通道'}`,
    `查询范围：${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    `位置记录：${locationCount.toLocaleString()}，有效定位：${validPointCount.toLocaleString()}`,
    `明细证据：${rawCount.toLocaleString()}，字段裁剪：${fieldCount.toLocaleString()}`,
    `区间里程：${formatNumber(mileageDelta, ' km')}，最高速度：${formatNumber(maxSpeed, ' km/h')}`,
    `起点：${firstLocation?.deviceTime || firstLocation?.serverTime || '-'}`,
    `终点：${lastLocation?.deviceTime || lastLocation?.serverTime || '-'}`,
    `异常：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `轨迹回放：${appURL(buildAppHash({ page: 'history', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `历史位置：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'fields', includeFields: 'true', ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {}) } }))}`,
    `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `车辆服务：${appURL(buildAppHash({ page: 'detail', keyword: vehicle, protocol }))}`
  ].join('\n');
}

function deliveryPackageText({
  filters,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  coverageRate,
  mileageDelta,
  anomalySummary
}: {
  filters: HistoryFilters;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  coverageRate?: number;
  mileageDelta?: number;
  anomalySummary: TrajectoryAnomalySummary;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const evidenceFilters = {
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {})
  };
  const anomalyTotal = anomalySummary.gapCount + anomalySummary.mileageRollbackCount + anomalySummary.overspeedCount;
  const deliveryState = locationCount > 0 || rawCount > 0
    ? anomalyTotal > 0 || (coverageRate ?? 100) < 80 ? '可交付，建议附带复核说明' : '可交付'
    : '待查询数据';
  return [
    '【客户查询导出包】',
    `交付状态：${deliveryState}`,
    '交付物：轨迹报告 / 位置历史 / 里程统计 / 明细证据 / 字段裁剪 / 质量提示',
    `车辆范围：${vehicle || '全部车辆'}`,
    `数据通道：${protocol || '全部数据通道'}`,
    `时间范围：${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    `位置历史：${locationCount.toLocaleString()} 条，有效定位 ${validPointCount.toLocaleString()}，覆盖率 ${formatPercent(coverageRate)}`,
    `明细证据：${rawCount.toLocaleString()} 帧`,
    `字段裁剪：${fieldCount.toLocaleString()} 个字段`,
    `区间里程：${formatNumber(mileageDelta, ' km')}`,
    `质量提示：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `位置导出：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'fields', includeFields: 'true', ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {}) } }))}`,
    `轨迹回放：${appURL(buildAppHash({ page: 'history', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `车辆服务：${appURL(buildAppHash({ page: 'detail', keyword: vehicle, protocol }))}`
  ].join('\n');
}

function deliveryCoverRecommendation(fieldCount: number, validPointCount: number, rawCount: number, locationCount: number) {
  if (fieldCount > 0 && validPointCount === 0) return '优先导出字段证据，并补查轨迹位置。';
  if (validPointCount > 0) return '优先交付轨迹报告和位置历史，再附带字段证据。';
  if (rawCount > 0) return '先交付明细证据，并补充客户需要的字段裁剪。';
  if (locationCount > 0) return '先交付位置历史，并补充里程统计说明。';
  return '先锁定车辆和时间窗，再查询位置或明细证据。';
}

function deliveryCoverText({
  filters,
  dateFrom,
  dateTo,
  deliveryState,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  anomalySummary
}: {
  filters: HistoryFilters;
  dateFrom?: string;
  dateTo?: string;
  deliveryState: string;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  anomalySummary: TrajectoryAnomalySummary;
}) {
  const vehicle = filters.keyword?.trim() || '全部车辆';
  const protocol = filters.protocol?.trim() || '全部来源证据';
  const rangeFrom = dateFrom?.trim() || filters.dateFrom?.trim() || '-';
  const rangeTo = dateTo?.trim() || filters.dateTo?.trim() || '-';
  const evidenceFilters = {
    ...(rangeFrom !== '-' ? { dateFrom: rangeFrom } : {}),
    ...(rangeTo !== '-' ? { dateTo: rangeTo } : {})
  };
  const keyword = filters.keyword?.trim() || '';
  const protocolFilter = filters.protocol?.trim() || '';
  return [
    '【客户数据交付封面包】',
    `交付主题：${vehicle} / ${protocol} / ${rangeFrom} 至 ${rangeTo}`,
    `交付结论：${deliveryState}`,
    '交付物：轨迹报告 / 位置历史 CSV / 明细证据 CSV / 字段裁剪 CSV / 里程统计链接 / 质量提示',
    `当前证据：位置 ${locationCount.toLocaleString()} 条 / 有效坐标 ${validPointCount.toLocaleString()} 点 / 明细 ${rawCount.toLocaleString()} 帧 / 字段 ${fieldCount.toLocaleString()} 个`,
    `质量提示：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `建议动作：${deliveryCoverRecommendation(fieldCount, validPointCount, rawCount, locationCount)}`,
    `轨迹报告：${appURL(buildAppHash({ page: 'history', keyword, protocol: protocolFilter, filters: evidenceFilters }))}`,
    `位置历史：${appURL(buildAppHash({ page: 'history-query', keyword, protocol: protocolFilter, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword, protocol: protocolFilter, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword, protocol: protocolFilter, filters: { ...evidenceFilters, tab: 'fields', includeFields: 'true', ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {}) } }))}`,
    `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword, protocol: protocolFilter, filters: evidenceFilters }))}`
  ].join('\n');
}

function scheduledHistoryReportPlanText({
  filters,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  anomalySummary
}: {
  filters: HistoryFilters;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  anomalySummary: TrajectoryAnomalySummary;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const evidenceFilters = {
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {})
  };
  return [
    '【历史数据周期交付计划】',
    `车辆范围：${vehicle || '全部车辆'}`,
    `数据通道：${protocol || '全部数据通道'}`,
    `时间范围：${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    '推荐频率：日报用于客户复盘，周报用于客户对账，临时导出用于问题解释。',
    '交付内容：轨迹报告 / 位置历史 CSV / 明细证据 CSV / 字段裁剪 CSV / 里程统计链接 / 质量提示。',
    `当前数据量：位置 ${locationCount.toLocaleString()} 条，有效定位 ${validPointCount.toLocaleString()}，明细证据 ${rawCount.toLocaleString()} 帧，字段裁剪 ${fieldCount.toLocaleString()} 个。`,
    `质量提示：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    '交付前检查：先锁定车辆和时间窗，再确认字段裁剪、分页覆盖和异常说明。',
    `轨迹报告：${appURL(buildAppHash({ page: 'history', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `位置历史：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'fields', includeFields: 'true', ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {}) } }))}`,
    `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword: vehicle, protocol, filters: evidenceFilters }))}`
  ].join('\n');
}

function savedHistoryReportViewText({
  filters,
  fieldCount,
  fieldPaths,
  locationCount,
  rawCount,
  validPointCount,
  anomalySummary
}: {
  filters: HistoryFilters;
  fieldCount: number;
  fieldPaths: string[];
  locationCount: number;
  rawCount: number;
  validPointCount: number;
  anomalySummary: TrajectoryAnomalySummary;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const fieldTemplate = fieldPaths.length > 0 ? fieldPaths.join(', ') : filters.fields?.trim() || '未配置字段裁剪';
  const evidenceFilters = {
    tab: 'fields',
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {}),
    includeFields: 'true',
    ...(fieldPaths.length > 0 ? { fields: fieldPaths.join(',') } : filters.fields?.trim() ? { fields: filters.fields.trim() } : {})
  };
  return [
    '【保存报表视图】',
    `视图范围：${vehicle || '全部车辆'} / ${protocol || '全部数据通道'} / ${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    `字段模板：${fieldTemplate}`,
    '交付节奏：日报用于客户复盘，周报用于客户对账，临时导出用于问题解释。',
    `当前证据：位置 ${locationCount.toLocaleString()} 条，有效定位 ${validPointCount.toLocaleString()}，明细 ${rawCount.toLocaleString()} 帧，字段 ${fieldCount.toLocaleString()} 个。`,
    `质量提示：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `复用链接：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: evidenceFilters }))}`
  ].join('\n');
}

function trajectoryReviewPackageText({
  filters,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  coverageRate,
  playbackSpanMinutes,
  playbackIntervalMinutes,
  mileageDelta,
  maxSpeed,
  firstLocation,
  lastLocation,
  currentPlayback,
  currentPlaybackIndex,
  playbackCount,
  currentAddress,
  anomalySummary
}: {
  filters: HistoryFilters;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  coverageRate?: number;
  playbackSpanMinutes?: number;
  playbackIntervalMinutes?: number;
  mileageDelta?: number;
  maxSpeed?: number;
  firstLocation?: HistoryLocationRow;
  lastLocation?: HistoryLocationRow;
  currentPlayback?: HistoryLocationRow;
  currentPlaybackIndex: number;
  playbackCount: number;
  currentAddress?: MapReverseGeocode;
  anomalySummary: TrajectoryAnomalySummary;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const evidenceFilters = {
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {})
  };
  const anomalyAction = anomalySummary.gapCount > 0 || anomalySummary.mileageRollbackCount > 0 || anomalySummary.overspeedCount > 0
    ? '建议核对明细证据、字段裁剪、当日里程统计，并确认平台是否存在断链或补发。'
    : '当前页未发现明显断点、里程回退或速度异常，可作为轨迹复盘依据使用。';
  return [
    '【客户轨迹复盘包】',
    `车辆：${vehicle || '全部车辆'}`,
    `数据通道：${protocol || '全部数据通道'}`,
    `查询范围：${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    `轨迹规模：位置 ${locationCount.toLocaleString()} / 有效定位 ${validPointCount.toLocaleString()} / 明细证据 ${rawCount.toLocaleString()} / 字段裁剪 ${fieldCount.toLocaleString()}`,
    `轨迹质量：定位覆盖率 ${formatPercent(coverageRate)} / 回放跨度 ${formatDurationMinutes(playbackSpanMinutes)} / 采样间隔 ${formatDurationMinutes(playbackIntervalMinutes, '/点')}`,
    `里程速度：区间里程 ${formatNumber(mileageDelta, ' km')} / 最高速度 ${formatNumber(maxSpeed, ' km/h')}`,
    `起点：${firstLocation?.deviceTime || firstLocation?.serverTime || '-'}`,
    `终点：${lastLocation?.deviceTime || lastLocation?.serverTime || '-'}`,
    `当前回放点：${currentPlayback ? `点 ${currentPlaybackIndex + 1}/${playbackCount}，${currentPlayback.deviceTime || currentPlayback.serverTime || '-'}，${formatNumber(currentPlayback.speedKmh, ' km/h')}，${formatNumber(currentPlayback.totalMileageKm, ' km')}` : '-'}`,
    `当前点地址：${currentAddress?.formattedAddress || '-'}`,
    `异常判读：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `最大断点：${formatDurationMinutes(anomalySummary.maxGapMinutes)}`,
    `最大里程回退：${formatNumber(anomalySummary.maxMileageRollbackKm, ' km')}`,
    `最高异常速度：${formatNumber(anomalySummary.maxSpeedKmh, ' km/h')}`,
    `下一步：${anomalyAction}`,
    `轨迹回放：${appURL(buildAppHash({ page: 'history', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `历史位置：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'fields', includeFields: 'true', ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {}) } }))}`,
    `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `车辆服务：${appURL(buildAppHash({ page: 'detail', keyword: vehicle, protocol }))}`
  ].join('\n');
}

function trajectoryImpactPackageText({
  filters,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  coverageRate,
  playbackSpanMinutes,
  playbackIntervalMinutes,
  mileageDelta,
  maxSpeed,
  anomalySummary,
  amapConfigured
}: {
  filters: HistoryFilters;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  coverageRate?: number;
  playbackSpanMinutes?: number;
  playbackIntervalMinutes?: number;
  mileageDelta?: number;
  maxSpeed?: number;
  anomalySummary: TrajectoryAnomalySummary;
  amapConfigured: boolean;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const evidenceFilters = {
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {})
  };
  const anomalyTotal = anomalySummary.gapCount + anomalySummary.mileageRollbackCount + anomalySummary.overspeedCount;
  const operationState = anomalyTotal > 0 || (coverageRate ?? 100) < 80 ? '需要复核' : '可用于业务回放';
  return [
    '【客户轨迹影响】',
    `车辆：${vehicle || '全部车辆'}`,
    `数据通道：${protocol || '全部数据通道'}`,
    `查询范围：${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    `业务状态：${operationState}`,
    `轨迹范围：位置 ${locationCount.toLocaleString()} / 有效定位 ${validPointCount.toLocaleString()} / 明细证据 ${rawCount.toLocaleString()} / 字段裁剪 ${fieldCount.toLocaleString()}`,
    `定位覆盖率：${formatPercent(coverageRate)}`,
    `回放跨度：${formatDurationMinutes(playbackSpanMinutes)}，采样间隔：${formatDurationMinutes(playbackIntervalMinutes, '/点')}`,
    `里程速度：${formatNumber(mileageDelta, ' km')} / ${formatNumber(maxSpeed, ' km/h')}`,
    `异常影响：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `地图能力：${amapConfigured ? '高德 JS API 已配置' : '高德 JS API 待配置'}`,
    `轨迹回放：${appURL(buildAppHash({ page: 'history', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `历史查询导出：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `车辆服务：${appURL(buildAppHash({ page: 'detail', keyword: vehicle, protocol }))}`
  ].join('\n');
}

function trajectoryCustomerDecisionText({
  filters,
  locationCount,
  rawCount,
  fieldCount,
  validPointCount,
  coverageRate,
  playbackSpanMinutes,
  playbackIntervalMinutes,
  mileageDelta,
  maxSpeed,
  firstLocation,
  lastLocation,
  anomalySummary,
  amapConfigured,
  deliveryState
}: {
  filters: HistoryFilters;
  locationCount: number;
  rawCount: number;
  fieldCount: number;
  validPointCount: number;
  coverageRate?: number;
  playbackSpanMinutes?: number;
  playbackIntervalMinutes?: number;
  mileageDelta?: number;
  maxSpeed?: number;
  firstLocation?: HistoryLocationRow;
  lastLocation?: HistoryLocationRow;
  anomalySummary: TrajectoryAnomalySummary;
  amapConfigured: boolean;
  deliveryState: string;
}) {
  const vehicle = filters.keyword?.trim() || '';
  const protocol = filters.protocol?.trim() || '';
  const evidenceFilters = {
    ...(filters.dateFrom?.trim() ? { dateFrom: filters.dateFrom.trim() } : {}),
    ...(filters.dateTo?.trim() ? { dateTo: filters.dateTo.trim() } : {})
  };
  const anomalyTotal = anomalySummary.gapCount + anomalySummary.mileageRollbackCount + anomalySummary.overspeedCount;
  const decision = locationCount > 0 || rawCount > 0
    ? anomalyTotal > 0 || (coverageRate ?? 100) < 80 ? '可复盘，需附异常说明' : '可直接复盘'
    : '待查询数据';
  return [
    '【客户轨迹决策说明】',
    `决策结论：${decision}`,
    `交付状态：${deliveryState}`,
    `车辆范围：${vehicle || '全部车辆'}`,
    `数据通道：${protocol || '全部数据通道'}`,
    `时间范围：${filters.dateFrom?.trim() || '-'} 至 ${filters.dateTo?.trim() || '-'}`,
    `轨迹规模：位置 ${locationCount.toLocaleString()} / 有效定位 ${validPointCount.toLocaleString()} / 明细证据 ${rawCount.toLocaleString()} / 字段裁剪 ${fieldCount.toLocaleString()}`,
    `定位覆盖：${formatPercent(coverageRate)}`,
    `回放跨度：${formatDurationMinutes(playbackSpanMinutes)}，采样间隔：${formatDurationMinutes(playbackIntervalMinutes, '/点')}`,
    `区间里程：${formatNumber(mileageDelta, ' km')}，最高速度：${formatNumber(maxSpeed, ' km/h')}`,
    `起点：${firstLocation?.deviceTime || firstLocation?.serverTime || '-'}`,
    `终点：${lastLocation?.deviceTime || lastLocation?.serverTime || '-'}`,
    `异常提示：断点 ${anomalySummary.gapCount.toLocaleString()} / 里程回退 ${anomalySummary.mileageRollbackCount.toLocaleString()} / 超速 ${anomalySummary.overspeedCount.toLocaleString()}`,
    `地图能力：${amapConfigured ? '高德地图可用' : '坐标预览'}`,
    '',
    '客户复盘路径：',
    '1. 先看轨迹覆盖：确认是否有足够有效坐标。',
    '2. 再看时间断点：断点会影响定位连续性和客户解释。',
    '3. 再看里程速度：区间里程、速度和轨迹方向需要能互相解释。',
    '4. 最后回到依据：异常点必须打开明细证据、字段裁剪和里程统计复核。',
    `轨迹回放：${appURL(buildAppHash({ page: 'history', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `历史查询导出：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
    `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
    `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword: vehicle, protocol, filters: { ...evidenceFilters, tab: 'fields', includeFields: 'true', ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {}) } }))}`,
    `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword: vehicle, protocol, filters: evidenceFilters }))}`,
    `车辆服务：${appURL(buildAppHash({ page: 'detail', keyword: vehicle, protocol }))}`
  ].join('\n');
}

function amapLocationName(row: HistoryLocationRow, fallback: string) {
  return encodeURIComponent(row.plate || row.vin || fallback);
}

function amapTrajectoryURL(points: HistoryLocationRow[]) {
  const validPoints = points.filter(hasValidCoordinate);
  if (validPoints.length === 0) return '';
  if (validPoints.length === 1) {
    const point = validPoints[0];
    return `https://uri.amap.com/marker?position=${point.longitude},${point.latitude}&name=${amapLocationName(point, '车辆位置')}&src=lingniu-vehicle-platform`;
  }
  const start = validPoints[0];
  const end = validPoints[validPoints.length - 1];
  return [
    'https://uri.amap.com/navigation?',
    `from=${start.longitude},${start.latitude},${amapLocationName(start, '轨迹起点')}`,
    `&to=${end.longitude},${end.latitude},${amapLocationName(end, '轨迹终点')}`,
    '&mode=car',
    '&policy=1',
    '&src=lingniu-vehicle-platform'
  ].join('');
}

async function copyText(value: string, label: string) {
  const text = value.trim();
  if (!text) {
    Toast.warning(`${label}为空`);
    return;
  }
  try {
    await navigator.clipboard.writeText(text);
    Toast.success(`已复制${label}`);
  } catch {
    Toast.error(`复制${label}失败`);
  }
}

export function History({
  mode = 'trajectory',
  initialVin,
  initialProtocol,
  initialTab,
  initialFilters = {},
  onFiltersChange,
  onOpenVehicle,
  onOpenMileage,
  onOpenRaw
}: {
  mode?: 'trajectory' | 'query';
  initialVin: string;
  initialProtocol?: string;
  initialTab?: string;
  initialFilters?: Record<string, string>;
  onFiltersChange?: (filters: HistoryFilters, tab?: HistoryTabKey) => void;
  onOpenVehicle: (vin: string, protocol?: string) => void;
  onOpenMileage?: (filters: Record<string, string>) => void;
  onOpenRaw?: (filters: Record<string, string>) => void;
}) {
  const initialHistoryFilters = mergeInitialFilters(initialVin, initialProtocol, initialFilters);
  const [filters, setFilters] = useState<HistoryFilters>(initialHistoryFilters);
  const [activeTab, setActiveTab] = useState<HistoryTabKey>(normalizeHistoryTab(initialTab));
  const [locations, setLocations] = useState<Page<HistoryLocationRow>>(defaultPage);
  const [rawFrames, setRawFrames] = useState<Page<RawFrameRow>>(defaultPage);
  const [selectedRaw, setSelectedRaw] = useState<RawFrameRow | null>(null);
  const [loadingLocations, setLoadingLocations] = useState(false);
  const [loadingRaw, setLoadingRaw] = useState(false);
  const [locationPagination, setLocationPagination] = useState({ currentPage: 1, pageSize: 10 });
  const [rawPagination, setRawPagination] = useState({ currentPage: 1, pageSize: 10 });
  const [playbackIndex, setPlaybackIndex] = useState(0);
  const [playbackPlaying, setPlaybackPlaying] = useState(false);
  const [playbackSpeedMs, setPlaybackSpeedMs] = useState(1200);
  const [reverseGeocodeByPoint, setReverseGeocodeByPoint] = useState<Record<string, MapReverseGeocode>>({});
  const [reverseGeocoding, setReverseGeocoding] = useState(false);

  const buildParams = (nextFilters: HistoryFilters, limit: number, offset: number, raw: boolean) => {
    const params = new URLSearchParams({ limit: String(limit), offset: String(offset) });
    if (nextFilters.keyword?.trim()) params.set('keyword', nextFilters.keyword.trim());
    if (nextFilters.protocol?.trim()) params.set('protocol', nextFilters.protocol.trim());
    if (nextFilters.dateFrom?.trim()) params.set('dateFrom', nextFilters.dateFrom.trim());
    if (nextFilters.dateTo?.trim()) params.set('dateTo', nextFilters.dateTo.trim());
    if (raw && isIncludeFieldsEnabled(nextFilters.includeFields)) params.set('includeFields', 'true');
    if (raw && nextFilters.fields?.trim()) params.set('fields', nextFilters.fields.trim());
    return params;
  };

  const buildRawQuery = (nextFilters: HistoryFilters, limit: number, offset: number, forceIncludeFields = false): RawFrameQuery => {
    const query: RawFrameQuery = { limit, offset };
    if (nextFilters.keyword?.trim()) query.keyword = nextFilters.keyword.trim();
    if (nextFilters.protocol?.trim()) query.protocol = nextFilters.protocol.trim();
    if (nextFilters.dateFrom?.trim()) query.dateFrom = nextFilters.dateFrom.trim();
    if (nextFilters.dateTo?.trim()) query.dateTo = nextFilters.dateTo.trim();
    if (forceIncludeFields || isIncludeFieldsEnabled(nextFilters.includeFields)) query.includeFields = true;
    const fields = splitFields(nextFilters.fields);
    if (fields.length > 0) query.fields = fields;
    return query;
  };

  const loadLocations = (nextFilters = filters, page = locationPagination.currentPage, pageSize = locationPagination.pageSize) => {
    setLoadingLocations(true);
    api.historyLocations(buildParams(nextFilters, pageSize, (page - 1) * pageSize, false))
      .then((nextPage) => {
        setLocations(nextPage);
        setLocationPagination({ currentPage: page, pageSize });
        setPlaybackIndex(0);
        setPlaybackPlaying(false);
        setReverseGeocodeByPoint({});
      })
      .catch((error: Error) => Toast.error(error.message))
      .finally(() => setLoadingLocations(false));
  };

  const loadRawFrames = (nextFilters = filters, page = rawPagination.currentPage, pageSize = rawPagination.pageSize, forceIncludeFields = activeTab === 'fields') => {
    if ((forceIncludeFields || isIncludeFieldsEnabled(nextFilters.includeFields)) && shouldBlockRawFieldQuery(nextFilters)) {
      setRawFrames({ items: [], total: 0, limit: pageSize, offset: (page - 1) * pageSize });
      setRawPagination({ currentPage: page, pageSize });
      Toast.warning('字段明细查询需要车辆、时间范围或字段裁剪');
      return;
    }
    setLoadingRaw(true);
    api.rawFramesQuery(buildRawQuery(nextFilters, pageSize, (page - 1) * pageSize, forceIncludeFields))
      .then((nextPage) => {
        setRawFrames(nextPage);
        setRawPagination({ currentPage: page, pageSize });
      })
      .catch((error: Error) => Toast.error(error.message))
      .finally(() => setLoadingRaw(false));
  };

  const submit = (values: Record<string, unknown>) => {
    const nextFilters: HistoryFilters = {
      keyword: String(values.keyword ?? ''),
      protocol: String(values.protocol ?? ''),
      dateFrom: String(values.dateFrom ?? ''),
      dateTo: String(values.dateTo ?? ''),
      fields: String(values.fields ?? ''),
      includeFields: Boolean(values.includeFields)
    };
    setFilters(nextFilters);
    onFiltersChange?.(nextFilters, activeTab);
    loadLocations(nextFilters, 1, locationPagination.pageSize);
    loadRawFrames(nextFilters, 1, rawPagination.pageSize, activeTab === 'fields');
  };

  const applyFilters = (nextFilters: HistoryFilters) => {
    setFilters(nextFilters);
    onFiltersChange?.(nextFilters, activeTab);
    loadLocations(nextFilters, 1, locationPagination.pageSize);
    loadRawFrames(nextFilters, 1, rawPagination.pageSize, activeTab === 'fields');
  };

  const reset = () => {
    const nextFilters = mergeInitialFilters(initialVin, initialProtocol, {});
    applyFilters(nextFilters);
  };

  useEffect(() => {
    const nextFilters = mergeInitialFilters(initialVin, initialProtocol, initialFilters);
    const nextTab = normalizeHistoryTab(initialTab);
    setFilters(nextFilters);
    setActiveTab(nextTab);
    loadLocations(nextFilters, 1, locationPagination.pageSize);
    loadRawFrames(nextFilters, 1, rawPagination.pageSize, nextTab === 'fields');
  }, [initialVin, initialProtocol, initialTab, JSON.stringify(initialFilters)]);

  const changeTab = (key: string) => {
    const nextTab = normalizeHistoryTab(String(key));
    const nextFilters = nextTab === 'fields' ? { ...filters, includeFields: true } : filters;
    setActiveTab(nextTab);
    setFilters(nextFilters);
    onFiltersChange?.(nextFilters, nextTab);
    if (nextTab === 'fields') {
      loadRawFrames(nextFilters, 1, rawPagination.pageSize, true);
    }
  };

  const rawFieldCount = useMemo(() => Object.keys(selectedRaw?.parsedFields ?? {}).length, [selectedRaw]);
  const rawItems = rawFrames.items ?? [];
  const rawFieldRows = useMemo<RawFieldRow[]>(() => rawItems.flatMap((row) => {
    const fields = flattenParsedFields(row.parsedFields ?? {});
    return fields.map((field, index) => ({
      id: `${row.id}-${field.path}-${index}`,
      rawId: row.id,
      vin: row.vin,
      plate: row.plate,
      protocol: row.protocol,
      frameType: row.frameType,
      deviceTime: row.deviceTime,
      serverTime: row.serverTime,
      fieldPath: field.path,
      fieldValue: field.value
    }));
  }), [rawItems]);
  const locationItems = locations.items ?? [];
  const validLocations = locationItems.filter(hasValidCoordinate);
  const mileageValues = locationItems.map((item) => item.totalMileageKm).filter(isFiniteNumber);
  const mileageDelta = mileageValues.length > 1 ? Math.max(...mileageValues) - Math.min(...mileageValues) : undefined;
  const maxSpeed = locationItems.map((item) => item.speedKmh).filter(isFiniteNumber).reduce<number | undefined>((max, item) => max == null ? item : Math.max(max, item), undefined);
  const firstLocation = locationItems[0];
  const lastLocation = locationItems[locationItems.length - 1];
  const trajectoryCoverageRate = locationItems.length > 0 ? (validLocations.length / locationItems.length) * 100 : undefined;
  const playbackStartMs = timeValue(firstLocation);
  const playbackEndMs = timeValue(lastLocation);
  const playbackSpanMinutes = playbackStartMs != null && playbackEndMs != null ? Math.abs(playbackEndMs - playbackStartMs) / 60000 : undefined;
  const playbackIntervalMinutes = isFiniteNumber(playbackSpanMinutes) && locationItems.length > 1 ? playbackSpanMinutes / (locationItems.length - 1) : undefined;
  const trajectoryAnomalies = useMemo(() => analyzeTrajectoryAnomalies(locationItems), [locationItems]);
  const hasTrajectoryAnomaly = trajectoryAnomalies.gapCount > 0 || trajectoryAnomalies.mileageRollbackCount > 0 || trajectoryAnomalies.overspeedCount > 0;
  const currentVehicleKeyword = filters.keyword?.trim() ?? '';
  const currentProtocol = filters.protocol?.trim() ?? '';
  const pageTitle = mode === 'query' ? '历史查询导出' : '轨迹回放';
  const pageDescription = mode === 'query'
    ? '按车辆和时间窗交付轨迹报告、位置记录、明细证据和字段裁剪，支持分页、字段裁剪和 CSV 导出'
    : '按车辆查询历史位置和轨迹回放，明细证据只作为复核依据，来源证据仅用于过滤';
  const scopeDescription = mode === 'query'
    ? '位置记录、明细证据和字段裁剪按当前车辆与时间范围分页查询。'
    : '历史位置和明细证据按当前车辆与时间范围查询。';
  const amapConfigured = isAMapConfigured();
  const selectedFieldCount = splitFields(filters.fields).length;
  const playbackRows = validLocations.length > 0 ? validLocations : locationItems;
  const currentPlaybackIndex = playbackRows.length === 0 ? -1 : Math.min(playbackIndex, playbackRows.length - 1);
  const currentPlayback = currentPlaybackIndex >= 0 ? playbackRows[currentPlaybackIndex] : undefined;
  const currentPlaybackPointId = currentPlayback ? playbackPointId(currentPlayback, currentPlaybackIndex) : undefined;
  const currentPlaybackAddress = currentPlaybackPointId ? reverseGeocodeByPoint[currentPlaybackPointId] : undefined;
  const playbackAtEnd = playbackRows.length > 0 && currentPlaybackIndex >= playbackRows.length - 1;
  const playbackPoints: VehicleMapPoint[] = validLocations.map((row, index) => ({
    id: playbackPointId(row, index),
    label: row.plate || row.vin || `点 ${index + 1}`,
    longitude: row.longitude,
    latitude: row.latitude,
    online: true,
    title: `${row.deviceTime || row.serverTime || '-'} ${row.speedKmh ?? '-'} km/h`
  }));
  const filterSummary = [
    currentVehicleKeyword ? `车辆：${currentVehicleKeyword}` : '',
    currentProtocol ? `来源证据：${currentProtocol}` : '',
    filters.dateFrom?.trim() ? `开始时间：${filters.dateFrom.trim()}` : '',
    filters.dateTo?.trim() ? `结束时间：${filters.dateTo.trim()}` : '',
    isIncludeFieldsEnabled(filters.includeFields) ? '返回字段明细' : '',
    selectedFieldCount > 0 ? `字段裁剪：${selectedFieldCount} 个` : ''
  ].filter(Boolean);
  const anomalyTotal = trajectoryAnomalies.gapCount + trajectoryAnomalies.mileageRollbackCount + trajectoryAnomalies.overspeedCount;
  const deliveryState = locations.total > 0 || (rawFrames.total ?? 0) > 0
    ? anomalyTotal > 0 || (trajectoryCoverageRate ?? 100) < 80 ? '可交付，需复核说明' : '可交付'
    : '待查询数据';
  const deliveryStateColor = deliveryState === '可交付' ? 'green' as const : deliveryState === '待查询数据' ? 'grey' as const : 'orange' as const;
  const trajectoryDecisionState = locations.total > 0 || (rawFrames.total ?? 0) > 0
    ? anomalyTotal > 0 || (trajectoryCoverageRate ?? 100) < 80 ? '可复盘，需说明' : '可直接复盘'
    : '待查询数据';
  const trajectoryDecisionColor = trajectoryDecisionState === '可直接复盘' ? 'green' as const : trajectoryDecisionState === '待查询数据' ? 'grey' as const : 'orange' as const;
  const deliveryScopeText = `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部来源证据'}`;
  const trajectoryDecisionItems = [
    {
      label: '复盘结论',
      value: trajectoryDecisionState,
      detail: trajectoryDecisionState === '可直接复盘' ? '轨迹连续性和明细覆盖可用于客户复盘。' : trajectoryDecisionState === '待查询数据' ? '先输入车辆和时间范围查询轨迹。' : '轨迹可用，但需要附带断点或异常说明。',
      color: trajectoryDecisionColor,
      action: '复制决策',
      disabled: false,
      onClick: () => copyTrajectoryDecision()
    },
    {
      label: '定位覆盖',
      value: formatPercent(trajectoryCoverageRate),
      detail: `${validLocations.length.toLocaleString()} / ${locations.total.toLocaleString()} 个有效坐标。`,
      color: (trajectoryCoverageRate ?? 0) >= 80 ? 'green' as const : locations.total > 0 ? 'orange' as const : 'grey' as const,
      action: '播放轨迹',
      disabled: validLocations.length === 0,
      onClick: () => openQueryTab('location')
    },
    {
      label: '时间断点',
      value: `${trajectoryAnomalies.gapCount.toLocaleString()} 项`,
      detail: trajectoryAnomalies.maxGapMinutes ? `最大断点 ${formatDurationMinutes(trajectoryAnomalies.maxGapMinutes)}。` : '当前页未发现超过 30 分钟断点。',
      color: trajectoryAnomalies.gapCount > 0 ? 'orange' as const : 'green' as const,
      action: '复盘包',
      disabled: false,
      onClick: () => copyTrajectoryReviewPackage()
    },
    {
      label: '里程速度',
      value: formatNumber(mileageDelta, ' km'),
      detail: `最高速度 ${formatNumber(maxSpeed, ' km/h')}，用于和里程统计互相解释。`,
      color: isFiniteNumber(mileageDelta) ? 'blue' as const : 'grey' as const,
      action: '里程统计',
      disabled: !onOpenMileage,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      label: '明细复核',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 个字段裁剪可导出。` : '异常点需要回到明细证据和字段裁剪。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      action: '明细证据',
      disabled: false,
      onClick: () => openQueryTab('raw')
    }
  ];
  const deliveryChecklistItems = [
    {
      title: '位置历史',
      value: `${locations.total.toLocaleString()} 条`,
      detail: `${validLocations.length.toLocaleString()} 个有效坐标，交付车辆定位明细。`,
      color: locations.total > 0 ? 'green' as const : 'grey' as const,
      action: '导出位置',
      disabled: locations.items.length === 0,
      onClick: () => exportLocations()
    },
    {
      title: '轨迹回放',
      value: validLocations.length > 0 ? '可回放' : '待查询',
      detail: '同一时间窗回放路线、速度、里程断点。',
      color: validLocations.length > 0 ? 'blue' as const : 'orange' as const,
      action: '查看轨迹',
      disabled: validLocations.length === 0,
      onClick: () => openQueryTab('location')
    },
    {
      title: '里程统计',
      value: formatNumber(mileageDelta, ' km'),
      detail: '进入里程统计核对区间里程和日统计闭合。',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      action: '里程统计',
      disabled: !onOpenMileage,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      title: '明细证据',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: '保留接入证据，用于解释字段值来源。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      action: '导出证据',
      disabled: rawFrames.items.length === 0,
      onClick: () => exportRawFrames()
    },
    {
      title: '字段裁剪',
      value: `${rawFieldRows.length.toLocaleString()} 个`,
      detail: selectedFieldCount > 0 ? `按 ${selectedFieldCount.toLocaleString()} 个字段裁剪。` : '可切换字段裁剪生成字段级交付。',
      color: rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      action: '字段配置',
      disabled: false,
      onClick: () => openQueryTab('fields')
    },
    {
      title: '质量提示',
      value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项` : '无明显异常',
      detail: `断点 ${trajectoryAnomalies.gapCount.toLocaleString()} / 里程回退 ${trajectoryAnomalies.mileageRollbackCount.toLocaleString()} / 超速 ${trajectoryAnomalies.overspeedCount.toLocaleString()}。`,
      color: anomalyTotal > 0 ? 'orange' as const : 'green' as const,
      action: '复制清单',
      disabled: false,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const timeWindowMonitorItems = [
    {
      title: '轨迹复盘',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '待查询',
      detail: `${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}，先确认位置覆盖和断点。`,
      action: '位置历史',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      title: '统计核对',
      value: formatNumber(mileageDelta, ' km'),
      detail: '把同一时间窗带到里程统计，核对区间里程和日报闭合。',
      action: '里程统计',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      disabled: !onOpenMileage,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      title: '历史证据',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: selectedFieldCount > 0 ? `包含 ${selectedFieldCount.toLocaleString()} 个字段裁剪。` : '保留原始明细和解析字段，解释位置、里程和字段来源。',
      action: '明细证据',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      disabled: false,
      onClick: () => openQueryTab('raw')
    },
    {
      title: '告警复盘',
      value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项提示` : '同步说明',
      detail: '用同一车辆和时间窗打开告警事件，解释断链、字段缺失和质量影响。',
      action: '告警说明',
      color: anomalyTotal > 0 ? 'orange' as const : 'blue' as const,
      disabled: false,
      onClick: () => {
        window.location.hash = buildAppHash({
          page: 'alert-events',
          keyword: currentVehicleKeyword,
          protocol: currentProtocol,
          filters: {
            ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}),
            ...(filters.dateTo ? { dateTo: filters.dateTo } : {})
          }
        });
      }
    }
  ];
  const historyReportTemplates = [
    {
      title: '轨迹报告',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '待查询',
      detail: '适合回答某辆车某段时间在哪里、怎么走、是否断点。',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      action: '复制报告',
      disabled: false,
      onClick: () => copyTrajectoryReviewPackage()
    },
    {
      title: '明细证据包',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: '适合客户或BI追溯位置、里程、速度、SOC等字段来源。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      action: '导出证据',
      disabled: false,
      onClick: () => openQueryTab('raw')
    },
    {
      title: '字段裁剪包',
      value: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 字段` : `${selectedFieldCount.toLocaleString()} 字段`,
      detail: selectedFieldCount > 0 ? '按客户指定字段裁剪，减少导出体积。' : '先填写字段裁剪或切换字段裁剪页，再生成字段级交付。',
      color: rawFieldRows.length > 0 || selectedFieldCount > 0 ? 'green' as const : 'orange' as const,
      action: '字段配置',
      disabled: false,
      onClick: () => openQueryTab('fields')
    },
    {
      title: '周期交付计划',
      value: '日报/周报',
      detail: '把同一查询范围沉淀为日报、周报或临时问题解释模板。',
      color: 'blue' as const,
      action: '复制计划',
      disabled: false,
      onClick: () => copyScheduledHistoryReportPlan()
    }
  ];
  const fieldEvidenceCount = rawFieldRows.length > 0 ? rawFieldRows.length : selectedFieldCount;
  const exportAcceptanceItems = [
    {
      label: '范围锁定',
      value: deliveryScopeText,
      detail: `${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}，用于限定客户交付边界。`,
      color: currentVehicleKeyword ? 'green' as const : 'orange' as const,
      onClick: () => openQueryTab(activeTab)
    },
    {
      label: '轨迹证据',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '0 点',
      detail: validLocations.length > 0 ? '轨迹点可用于回放和位置 CSV。' : '缺少轨迹点时先补查位置历史。',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      onClick: () => openQueryTab('location')
    },
    {
      label: '明细证据',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: '保留原始接入明细，解释位置、里程和字段来源。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      onClick: () => openQueryTab('raw')
    },
    {
      label: '字段裁剪',
      value: fieldEvidenceCount > 0 ? `${fieldEvidenceCount.toLocaleString()} 字段` : '待配置',
      detail: fieldEvidenceCount > 0 ? '字段证据已裁剪，可减少客户导出体积。' : '选择客户需要字段后再交付。',
      color: fieldEvidenceCount > 0 ? 'green' as const : 'orange' as const,
      onClick: () => openQueryTab('fields')
    },
    {
      label: '异常说明',
      value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项` : deliveryState,
      detail: anomalyTotal > 0 ? '复制交付说明时带上复核提示。' : '可直接生成客户可读交付说明。',
      color: anomalyTotal > 0 ? 'orange' as const : deliveryStateColor,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const exportAcceptanceActions = [
    {
      label: '复制验收',
      action: '交付说明',
      detail: '把范围、证据和异常说明复制给客户或内部复核。',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    },
    {
      label: '导出位置',
      action: '位置CSV',
      detail: '导出当前页位置点，作为轨迹回放明细。',
      color: locations.items.length > 0 ? 'green' as const : 'grey' as const,
      disabled: locations.items.length === 0,
      onClick: () => exportLocations()
    },
    {
      label: '导出字段',
      action: '字段CSV',
      detail: '导出客户指定字段，减少无关原始数据暴露。',
      color: rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      disabled: rawFieldRows.length === 0,
      onClick: () => exportRawFields()
    },
    {
      label: '查看明细',
      action: '明细证据',
      detail: '回到原始明细，确认字段和报文来源。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      disabled: false,
      onClick: () => openQueryTab('raw')
    }
  ];
  const customerExportCenterItems = [
    {
      title: '行程历史',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '0 点',
      detail: '交付轨迹路线、位置点、速度、里程断点和时间范围。',
      action: '导出轨迹',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: locations.items.length === 0,
      onClick: () => exportLocations()
    },
    {
      title: '里程对账',
      value: formatNumber(mileageDelta, ' km'),
      detail: '跳到同一车辆和时间窗的里程统计，核对区间差值和日报闭合。',
      action: '里程统计',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      disabled: !onOpenMileage,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      title: '字段证据',
      value: `${fieldEvidenceCount.toLocaleString()} 字段`,
      detail: '按客户指定字段裁剪导出，避免把完整原始 JSON 暴露给客户。',
      action: '导出字段',
      color: fieldEvidenceCount > 0 ? 'green' as const : 'orange' as const,
      disabled: rawFieldRows.length === 0,
      onClick: () => exportRawFields()
    },
    {
      title: '告警说明',
      value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项异常` : '无明显异常',
      detail: '复制交付说明，附带断点、里程回退、超速和证据链接。',
      action: '复制说明',
      color: anomalyTotal > 0 ? 'orange' as const : 'green' as const,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const vehicleHistoryServiceItems = [
    {
      label: '路线复盘',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '0 点',
      detail: '先看同一时间窗内的路线、速度和断点。',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      action: '查询位置',
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      label: '里程核对',
      value: formatNumber(mileageDelta, ' km'),
      detail: '带着车辆和时间窗进入统计页，核对区间里程。',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      action: '里程统计',
      disabled: !onOpenMileage,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      label: '明细证据',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: '查看接入明细，解释位置、里程和字段来源。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      action: '查看证据',
      disabled: false,
      onClick: () => openQueryTab('raw')
    },
    {
      label: '字段裁剪',
      value: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 字段` : `${selectedFieldCount.toLocaleString()} 字段`,
      detail: '按客户需要裁剪字段，减少导出体积。',
      color: rawFieldRows.length > 0 || selectedFieldCount > 0 ? 'green' as const : 'orange' as const,
      action: '字段配置',
      disabled: false,
      onClick: () => openQueryTab('fields')
    },
    {
      label: '客户导出',
      value: deliveryState,
      detail: '复制交付说明，或继续导出 CSV 证据。',
      color: deliveryStateColor,
      action: '复制交付',
      disabled: false,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const customerEvidencePackageOverviewItems = [
    {
      label: '交付范围',
      value: `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部来源证据'}`,
      detail: `${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}，先确认客户要查的车辆和时间窗。`,
      action: '查看范围',
      color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
      disabled: false,
      onClick: () => document.querySelector<HTMLInputElement>('input[name="keyword"]')?.focus()
    },
    {
      label: '轨迹证据',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '0 点',
      detail: validLocations.length > 0 ? '可进入轨迹回放和位置历史导出。' : '当前范围没有有效轨迹点，需要补查位置历史。',
      action: '查询轨迹',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      label: '明细字段',
      value: fieldEvidenceCount > 0 ? `${fieldEvidenceCount.toLocaleString()} 字段` : `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: fieldEvidenceCount > 0 ? '字段证据已裁剪，可按客户口径导出。' : '可查看历史明细并配置字段裁剪。',
      action: fieldEvidenceCount > 0 ? '字段导出' : '明细证据',
      color: fieldEvidenceCount > 0 ? 'green' as const : (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      disabled: false,
      onClick: () => fieldEvidenceCount > 0 ? openQueryTab('fields') : openQueryTab('raw')
    },
    {
      label: '交付说明',
      value: deliveryState,
      detail: anomalyTotal > 0 ? `存在 ${anomalyTotal.toLocaleString()} 项质量提示，交付说明需附带异常解释。` : '当前范围可生成客户可读的证据说明。',
      action: '复制说明',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const customerDeliveryReadinessItems = [
    {
      label: currentVehicleKeyword ? '范围已锁定' : '范围待选择',
      value: currentVehicleKeyword || '全部车辆',
      detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}`,
      action: '查看范围',
      color: currentVehicleKeyword ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => document.querySelector<HTMLInputElement>('input[name="keyword"]')?.focus()
    },
    {
      label: validLocations.length > 0 ? '轨迹可交付' : '轨迹待补',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '0 点',
      detail: validLocations.length > 0 ? '可以进入轨迹回放并导出客户路线。' : '当前时间窗没有轨迹点，先补查位置历史。',
      action: '查询轨迹',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      label: fieldEvidenceCount > 0 ? '字段已裁剪' : '字段待配置',
      value: fieldEvidenceCount > 0 ? `${fieldEvidenceCount.toLocaleString()} 字段` : '待配置',
      detail: fieldEvidenceCount > 0 ? '只交付客户需要的字段，减少证据包体积。' : '先选择客户需要的字段，再导出 CSV。',
      action: fieldEvidenceCount > 0 ? '字段导出' : '字段配置',
      color: fieldEvidenceCount > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('fields')
    },
    {
      label: '说明可复制',
      value: deliveryState,
      detail: anomalyTotal > 0 ? `附带 ${anomalyTotal.toLocaleString()} 项质量提示。` : '可直接复制客户可读的交付说明。',
      action: '复制说明',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const customerReportCadenceItems = [
    {
      title: '每日客户复盘',
      value: `${locations.total.toLocaleString()} 位置`,
      detail: '每天交付位置历史、轨迹覆盖、里程统计和质量提示，便于客户日报复盘。',
      action: '日报导出',
      color: locations.total > 0 ? 'green' as const : 'orange' as const,
      disabled: locations.items.length === 0,
      onClick: () => exportLocations()
    },
    {
      title: '每周客户对账',
      value: '周报计划',
      detail: '沉淀固定车辆范围、时间窗和交付物，按周给客户对账和复核。',
      action: '周报计划',
      color: 'blue' as const,
      disabled: false,
      onClick: () => copyScheduledHistoryReportPlan()
    },
    {
      title: '临时问题解释',
      value: deliveryState,
      detail: '遇到定位、里程或断链质疑时，复制交付说明并附上证据链接。',
      action: '复制说明',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    },
    {
      title: '字段模板复用',
      value: selectedFieldCount > 0 ? `${selectedFieldCount.toLocaleString()} 字段` : '待配置',
      detail: '复用字段裁剪模板，减少 CSV 体积并保证客户拿到稳定字段。',
      action: '字段配置',
      color: selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('fields')
    }
  ];
  const customerExportTemplateLibraryItems = [
    {
      title: '日报复盘包',
      value: '客户日报',
      detail: '每天交付车辆位置覆盖、轨迹断点、字段证据和质量提示，适合客户日报复盘。',
      action: '导出日报',
      color: locations.total > 0 ? 'green' as const : 'orange' as const,
      disabled: locations.items.length === 0,
      onClick: () => exportLocations()
    },
    {
      title: '周报对账包',
      value: '客户对账',
      detail: '按固定车辆范围沉淀周报计划，交付轨迹、里程统计链接和异常说明。',
      action: '复制周报计划',
      color: 'blue' as const,
      disabled: false,
      onClick: () => copyScheduledHistoryReportPlan()
    },
    {
      title: '问题解释包',
      value: '客户问询',
      detail: '客户质疑定位、里程或断链时，复制一份包含证据链接的说明。',
      action: '复制说明',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    },
    {
      title: '字段稳定包',
      value: '数据对接',
      detail: '把客户固定字段保存成模板，后续导出保持字段口径和顺序稳定。',
      action: '字段配置',
      color: selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('fields')
    }
  ];
  const customerReportPurposeItems = [
    {
      title: '日报复盘',
      value: '客户日报',
      detail: '每天快速交付车辆位置、轨迹覆盖、区间里程和质量提示。',
      action: '生成日报',
      color: locations.total > 0 ? 'green' as const : 'orange' as const,
      disabled: locations.items.length === 0,
      onClick: () => exportLocations()
    },
    {
      title: '周报对账',
      value: '客户对账',
      detail: '把固定车辆范围和时间窗沉淀为周报计划，给客户对账复核。',
      action: '生成周报',
      color: 'blue' as const,
      disabled: false,
      onClick: () => copyScheduledHistoryReportPlan()
    },
    {
      title: '临时解释',
      value: '质量说明',
      detail: '遇到定位、里程或断链质疑时，复制带证据链接的解释说明。',
      action: '复制说明',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    },
    {
      title: '字段模板',
      value: '稳定字段',
      detail: '按客户固定字段裁剪导出，减少体积并保证每次字段一致。',
      action: '字段配置',
      color: selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('fields')
    }
  ];
  const savedReportViewItems = [
    {
      title: '当前筛选',
      value: currentVehicleKeyword || '全部车辆',
      detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}`,
      action: '保存视图',
      color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
      disabled: false,
      onClick: () => copySavedReportView()
    },
    {
      title: '字段模板',
      value: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 字段` : `${selectedFieldCount.toLocaleString()} 字段`,
      detail: filters.fields?.trim() || '未配置字段裁剪，默认使用当前字段明细。',
      action: '复用字段',
      color: selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('fields')
    },
    {
      title: '交付节奏',
      value: '日报/周报',
      detail: '日报用于客户复盘，周报用于客户对账，临时导出用于问题解释。',
      action: '复制计划',
      color: 'blue' as const,
      disabled: false,
      onClick: () => copyScheduledHistoryReportPlan()
    },
    {
      title: '快速分享',
      value: '可复制',
      detail: '复制当前视图链接和字段模板，交给客户或内部服务团队复用。',
      action: '复制链接',
      color: 'blue' as const,
      disabled: false,
      onClick: () => copySavedReportView()
    }
  ];
  const customerHistoryQuestions = [
    {
      question: '这辆车这段时间在哪里？',
      answer: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 个有效轨迹点` : '先查询位置历史',
      evidence: `${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}`,
      action: '轨迹回放',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      question: '这段时间跑了多少公里？',
      answer: formatNumber(mileageDelta, ' km'),
      evidence: `最高速度 ${formatNumber(maxSpeed, ' km/h')}`,
      action: '里程统计',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      disabled: !onOpenMileage,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      question: '为什么定位或里程不连续？',
      answer: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项需复核` : '当前页无明显异常',
      evidence: `断点 ${trajectoryAnomalies.gapCount.toLocaleString()} / 里程回退 ${trajectoryAnomalies.mileageRollbackCount.toLocaleString()} / 超速 ${trajectoryAnomalies.overspeedCount.toLocaleString()}`,
      action: '复核证据',
      color: anomalyTotal > 0 ? 'orange' as const : 'green' as const,
      disabled: false,
      onClick: () => copyTrajectoryDecision()
    },
    {
      question: '明细能导出给客户吗？',
      answer: deliveryState,
      evidence: `位置 ${locations.total.toLocaleString()} / 明细 ${(rawFrames.total ?? 0).toLocaleString()} / 字段 ${rawFieldRows.length.toLocaleString()}`,
      action: '导出交付包',
      color: deliveryStateColor,
      disabled: false,
      onClick: () => copyDeliveryPackage()
    }
  ];
  const clearRangeFilters = () => {
    applyFilters(currentVehicleKeyword ? { keyword: currentVehicleKeyword } : {});
  };
  const exportLocations = () => {
    if (locations.items.length === 0) {
      Toast.warning('当前没有可导出的位置历史');
      return;
    }
    downloadCsv(exportFileName('history-locations', filters), buildCsv(locationExportColumns, locations.items));
    Toast.success(`已导出 ${locations.items.length} 条位置历史`);
  };
  const exportRawFrames = () => {
    if (rawFrames.items.length === 0) {
      Toast.warning('当前没有可导出的历史明细');
      return;
    }
    downloadCsv(exportFileName('raw-frames', filters), buildCsv(rawExportColumns, rawFrames.items));
    Toast.success(`已导出 ${rawFrames.items.length} 条历史明细`);
  };
  const exportRawFields = () => {
    if (rawFieldRows.length === 0) {
      Toast.warning('当前没有可导出的字段明细');
      return;
    }
    downloadCsv(exportFileName('raw-fields', filters), buildCsv(rawFieldExportColumns, rawFieldRows));
    Toast.success(`已导出 ${rawFieldRows.length} 条字段明细`);
  };
  const copyTrajectorySummary = () => {
    copyText(
      trajectorySummaryText({
        filters,
        totalPoints: locations.total,
        validPointCount: validLocations.length,
        mileageDelta,
        maxSpeed,
        firstLocation,
        lastLocation
      }),
      '轨迹摘要'
    );
  };
  const copyHistoryEvidencePackage = () => {
    copyText(
      historyEvidencePackageText({
        filters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        mileageDelta,
        maxSpeed,
        firstLocation,
        lastLocation,
        anomalySummary: trajectoryAnomalies
      }),
      '数据复核包'
    );
  };
  const copyDeliveryPackage = () => {
    copyText(
      deliveryPackageText({
        filters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        coverageRate: trajectoryCoverageRate,
        mileageDelta,
        anomalySummary: trajectoryAnomalies
      }),
      '客户查询导出包'
    );
  };
  const evidenceDateFrom = filters.dateFrom?.trim()
    || dateOnly(rawFieldRows[0]?.deviceTime || rawItems[0]?.deviceTime || firstLocation?.deviceTime || firstLocation?.serverTime);
  const evidenceDateTo = filters.dateTo?.trim() || (evidenceDateFrom ? nextDate(evidenceDateFrom) : '');
  const exportWizardEvidenceLabel = rawFieldRows.length > 0
    ? '字段证据'
    : validLocations.length > 0
      ? '轨迹报告'
      : (rawFrames.total ?? 0) > 0
        ? '明细证据'
        : '待查询证据';
  const copyDeliveryCover = () => {
    copyText(
      deliveryCoverText({
        filters,
        dateFrom: evidenceDateFrom,
        dateTo: evidenceDateTo,
        deliveryState,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        anomalySummary: trajectoryAnomalies
      }),
      '客户数据交付封面包'
    );
  };
  const copyExportWizardPackage = () => {
    const evidenceFilters = {
      ...(evidenceDateFrom ? { dateFrom: evidenceDateFrom } : {}),
      ...(evidenceDateTo ? { dateTo: evidenceDateTo } : {})
    };
    const fieldFilters = {
      tab: 'fields',
      ...evidenceFilters,
      includeFields: 'true',
      ...(filters.fields?.trim() ? { fields: filters.fields.trim() } : {})
    };
    const lines = [
      '【客户导出向导包】',
      `步骤1 锁定范围：${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部来源证据'} / ${evidenceDateFrom || '-'} 至 ${evidenceDateTo || '-'}`,
      `步骤2 选择证据：${exportWizardEvidenceLabel}`,
      `步骤3 质量复核：${deliveryState}`,
      '步骤4 导出交付：复制交付包',
      `位置历史：${appURL(buildAppHash({ page: 'history-query', keyword: currentVehicleKeyword, protocol: currentProtocol, filters: { ...evidenceFilters, tab: 'location' } }))}`,
      `明细证据：${appURL(buildAppHash({ page: 'history-query', keyword: currentVehicleKeyword, protocol: currentProtocol, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
      `字段裁剪：${appURL(buildAppHash({ page: 'history-query', keyword: currentVehicleKeyword, protocol: currentProtocol, filters: fieldFilters }))}`,
      `轨迹回放：${appURL(buildAppHash({ page: 'history', keyword: currentVehicleKeyword, protocol: currentProtocol, filters: evidenceFilters }))}`,
      `里程统计：${appURL(buildAppHash({ page: 'mileage', keyword: currentVehicleKeyword, protocol: currentProtocol, filters: evidenceFilters }))}`
    ];
    copyText(lines.join('\n'), '客户导出向导包');
  };
  const exportWizardSteps = [
    {
      step: '1',
      title: '锁定范围',
      value: `${currentVehicleKeyword || '全部车辆'} / ${currentProtocol || '全部来源证据'}`,
      detail: `${evidenceDateFrom || '-'} 至 ${evidenceDateTo || '-'}，先确认客户要查的车辆和时间窗。`,
      color: currentVehicleKeyword ? 'green' as const : 'orange' as const,
      onClick: () => document.querySelector<HTMLInputElement>('input[name="keyword"]')?.focus()
    },
    {
      step: '2',
      title: '选择证据',
      value: exportWizardEvidenceLabel,
      detail: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 个字段可按客户口径导出。` : `${validLocations.length.toLocaleString()} 个有效坐标，${(rawFrames.total ?? 0).toLocaleString()} 帧明细。`,
      color: rawFieldRows.length > 0 || validLocations.length > 0 ? 'blue' as const : 'orange' as const,
      onClick: () => rawFieldRows.length > 0 ? openQueryTab('fields') : validLocations.length > 0 ? openQueryTab('location') : openQueryTab('raw')
    },
    {
      step: '3',
      title: '质量复核',
      value: deliveryState,
      detail: anomalyTotal > 0 ? `存在 ${anomalyTotal.toLocaleString()} 项质量提示，交付时附说明。` : '当前范围可生成客户可读交付说明。',
      color: deliveryStateColor,
      onClick: () => copyDeliveryCover()
    },
    {
      step: '4',
      title: '导出交付',
      value: '复制交付包',
      detail: '把位置、明细、字段、轨迹和统计链接打包给客户或内部复核。',
      color: 'blue' as const,
      onClick: () => copyExportWizardPackage()
    }
  ];
  const copyScheduledHistoryReportPlan = () => {
    copyText(
      scheduledHistoryReportPlanText({
        filters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        anomalySummary: trajectoryAnomalies
      }),
      '历史数据周期交付计划'
    );
  };
  const copySavedReportView = () => {
    const fieldPaths = Array.from(new Set(rawFieldRows.map((row) => row.fieldPath).filter(Boolean)));
    const fallbackDateFrom = dateOnly(rawFieldRows[0]?.deviceTime);
    const fallbackDateTo = fallbackDateFrom ? nextDate(fallbackDateFrom) : '';
    const viewFilters = {
      ...filters,
      ...(filters.dateFrom?.trim() ? {} : fallbackDateFrom ? { dateFrom: fallbackDateFrom } : {}),
      ...(filters.dateTo?.trim() ? {} : fallbackDateTo ? { dateTo: fallbackDateTo } : {}),
      ...(fieldPaths.length > 0 ? { fields: fieldPaths.join(',') } : {})
    };
    copyText(
      savedHistoryReportViewText({
        filters: viewFilters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        fieldPaths,
        validPointCount: validLocations.length,
        anomalySummary: trajectoryAnomalies
      }),
      '保存报表视图'
    );
  };
  const copyTrajectoryReviewPackage = () => {
    copyText(
      trajectoryReviewPackageText({
        filters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        coverageRate: trajectoryCoverageRate,
        playbackSpanMinutes,
        playbackIntervalMinutes,
        mileageDelta,
        maxSpeed,
        firstLocation,
        lastLocation,
        currentPlayback,
        currentPlaybackIndex,
        playbackCount: playbackRows.length,
        currentAddress: currentPlaybackAddress,
        anomalySummary: trajectoryAnomalies
      }),
      '客户轨迹复盘包'
    );
  };
  const copyTrajectoryImpactPackage = () => {
    copyText(
      trajectoryImpactPackageText({
        filters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        coverageRate: trajectoryCoverageRate,
        playbackSpanMinutes,
        playbackIntervalMinutes,
        mileageDelta,
        maxSpeed,
        anomalySummary: trajectoryAnomalies,
        amapConfigured
      }),
      '客户轨迹影响'
    );
  };
  const copyTrajectoryDecision = () => {
    copyText(
      trajectoryCustomerDecisionText({
        filters,
        locationCount: locations.total ?? 0,
        rawCount: rawFrames.total ?? 0,
        fieldCount: rawFieldRows.length,
        validPointCount: validLocations.length,
        coverageRate: trajectoryCoverageRate,
        playbackSpanMinutes,
        playbackIntervalMinutes,
        mileageDelta,
        maxSpeed,
        firstLocation,
        lastLocation,
        anomalySummary: trajectoryAnomalies,
        amapConfigured,
        deliveryState
      }),
      '客户轨迹决策说明'
    );
  };
  const openAmapTrajectory = () => {
    const url = amapTrajectoryURL(validLocations);
    if (!url) {
      Toast.warning('当前轨迹没有有效坐标');
      return;
    }
    window.open(url, '_blank', 'noopener,noreferrer');
  };
  const resolveCurrentPlaybackAddress = () => {
    if (!currentPlayback || !currentPlaybackPointId || !hasValidCoordinate(currentPlayback)) {
      Toast.warning('当前回放点没有有效坐标');
      return;
    }
    if (currentPlaybackAddress) {
      Toast.info('当前回放点地址已解析');
      return;
    }
    const params = new URLSearchParams({
      longitude: String(currentPlayback.longitude),
      latitude: String(currentPlayback.latitude)
    });
    setReverseGeocoding(true);
    api.reverseGeocode(params)
      .then((address) => {
        setReverseGeocodeByPoint((current) => ({ ...current, [currentPlaybackPointId]: address }));
        Toast.success('已解析当前点地址');
      })
      .catch((error: Error) => Toast.error(error.message))
      .finally(() => setReverseGeocoding(false));
  };
  useEffect(() => {
    if (!playbackPlaying || playbackRows.length <= 1) return undefined;
    const timer = window.setInterval(() => {
      setPlaybackIndex((current) => {
        if (current >= playbackRows.length - 1) {
          setPlaybackPlaying(false);
          return current;
        }
        const next = current + 1;
        if (next >= playbackRows.length - 1) setPlaybackPlaying(false);
        return next;
      });
    }, playbackSpeedMs);
    return () => window.clearInterval(timer);
  }, [playbackPlaying, playbackRows.length, playbackSpeedMs]);
  const togglePlayback = () => {
    if (playbackPlaying) {
      setPlaybackPlaying(false);
      return;
    }
    if (playbackRows.length <= 1) return;
    if (playbackAtEnd) setPlaybackIndex(0);
    setPlaybackPlaying(true);
  };
  const movePlayback = (delta: number) => {
    if (playbackRows.length === 0) return;
    setPlaybackPlaying(false);
    setPlaybackIndex((current) => Math.min(Math.max(current + delta, 0), playbackRows.length - 1));
  };
  const selectPlaybackPoint = (nextIndex: number) => {
    if (playbackRows.length === 0) return;
    setPlaybackPlaying(false);
    setPlaybackIndex(Math.min(Math.max(nextIndex, 0), playbackRows.length - 1));
  };
  const selectPlaybackMapPoint = (point: VehicleMapPoint) => {
    const index = playbackRows.findIndex((row, rowIndex) => playbackPointId(row, rowIndex) === point.id);
    if (index >= 0) {
      selectPlaybackPoint(index);
    }
  };
  const openPlaybackVehicle = () => {
    if (!currentPlayback || !canOpenVehicle(currentPlayback.vin)) return;
    onOpenVehicle(currentPlayback.vin, currentPlayback.protocol);
  };
  const openLocationMileage = (row: HistoryLocationRow) => {
    if (!canOpenVehicle(row.vin)) return;
    const day = dateOnly(row.deviceTime || row.serverTime || row.lastSeen);
    onOpenMileage?.({
      keyword: row.vin,
      protocol: row.protocol,
      ...(day ? { dateFrom: day, dateTo: nextDate(day) } : {})
    });
  };
  const openLocationRaw = (row: HistoryLocationRow) => {
    if (!canOpenVehicle(row.vin)) return;
    const day = dateOnly(row.deviceTime || row.serverTime || row.lastSeen);
    onOpenRaw?.({
      keyword: row.vin,
      protocol: row.protocol,
      ...(day ? { dateFrom: day, dateTo: nextDate(day) } : {}),
      includeFields: 'true'
    });
  };
  const openRawMileage = (row: RawFrameRow) => {
    if (!canOpenVehicle(row.vin)) return;
    const day = dateOnly(row.deviceTime || row.serverTime);
    onOpenMileage?.({
      keyword: row.vin,
      protocol: row.protocol,
      ...(day ? { dateFrom: day, dateTo: nextDate(day) } : {})
    });
  };
  const openQueryTab = (tab: HistoryTabKey) => {
    const nextFilters = tab === 'fields' ? { ...filters, includeFields: true } : filters;
    setActiveTab(tab);
    setFilters(nextFilters);
    onFiltersChange?.(nextFilters, tab);
    if (tab === 'location') {
      loadLocations(nextFilters, 1, locationPagination.pageSize);
      return;
    }
    loadRawFrames(nextFilters, 1, rawPagination.pageSize, tab === 'fields');
  };
  const customerTimeWindowEvidenceItems = [
    {
      title: '轨迹复盘',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '待查询',
      detail: '先用车辆地图和轨迹回放解释这段时间车辆在哪里、怎么走。',
      action: '查看轨迹',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      title: '里程核对',
      value: formatNumber(mileageDelta, ' km'),
      detail: '把同一车辆和时间窗带到统计页，核对区间差值与日报闭合。',
      action: '里程统计',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      disabled: !onOpenMileage || !currentVehicleKeyword,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      title: '历史导出',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: '下钻到历史明细和位置记录，保留客户问责时可复核的证据。',
      action: '明细证据',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      disabled: false,
      onClick: () => openQueryTab('raw')
    },
    {
      title: '字段裁剪',
      value: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 字段` : `${selectedFieldCount.toLocaleString()} 字段`,
      detail: '按客户只需要的字段生成交付视图，避免暴露完整 RAW 结构。',
      action: '字段导出',
      color: rawFieldRows.length > 0 || selectedFieldCount > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('fields')
    },
    {
      title: '告警复盘',
      value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项` : '可说明',
      detail: '同一时间窗查看断链、离线、定位异常和通知闭环。',
      action: '告警事件',
      color: anomalyTotal > 0 ? 'orange' as const : 'blue' as const,
      disabled: false,
      onClick: () => {
        window.location.hash = buildAppHash({
          page: 'alert-events',
          keyword: currentVehicleKeyword,
          protocol: currentProtocol,
          filters: {
            ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}),
            ...(filters.dateTo ? { dateTo: filters.dateTo } : {})
          }
        });
      }
    }
  ];
  const customerReplayNavigation = [
    {
      title: '选车和时间',
      value: currentVehicleKeyword || '待选择车辆',
      detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}`,
      action: '调整筛选',
      color: currentVehicleKeyword ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => document.querySelector<HTMLInputElement>('input[name="keyword"]')?.focus()
    },
    {
      title: '回放路线',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '待查询',
      detail: '按时间顺序播放轨迹，核对路线、停留和断点。',
      action: '播放轨迹',
      color: validLocations.length > 0 ? 'green' as const : 'grey' as const,
      disabled: validLocations.length === 0,
      onClick: () => openQueryTab('location')
    },
    {
      title: '核对里程',
      value: formatNumber(mileageDelta, ' km'),
      detail: '跳到同一车辆和时间窗的里程统计，检查区间差值。',
      action: '里程统计',
      color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const,
      disabled: !onOpenMileage || !currentVehicleKeyword,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      title: '导出证据',
      value: deliveryState,
      detail: '复制客户交付包，包含轨迹、位置、RAW、字段和统计链接。',
      action: '复制交付',
      color: deliveryStateColor,
      disabled: false,
      onClick: copyDeliveryPackage
    }
  ];
  const deliveryCoverItems = [
    {
      label: '交付结论',
      value: deliveryState,
      detail: deliveryState === '可交付' ? '当前证据可形成客户交付包。' : deliveryState === '待查询数据' ? '先查询位置或明细数据。' : '可交付，但需要附带复核说明。',
      color: deliveryStateColor,
      action: '复制封面',
      disabled: false,
      onClick: copyDeliveryCover
    },
    {
      label: '轨迹报告',
      value: `${validLocations.length.toLocaleString()} 点`,
      detail: validLocations.length > 0 ? '可交付轨迹报告和位置历史。' : '当前缺少有效轨迹点，建议补查位置历史。',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      action: validLocations.length > 0 ? '查看轨迹' : '补查轨迹',
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      label: '字段证据',
      value: `${fieldEvidenceCount.toLocaleString()} 字段`,
      detail: fieldEvidenceCount > 0 ? '字段证据已裁剪，可用于客户交付。' : '先选择客户需要字段，减少导出体积。',
      color: fieldEvidenceCount > 0 ? 'green' as const : 'orange' as const,
      action: rawFieldRows.length > 0 ? '导出字段' : '字段配置',
      disabled: false,
      onClick: rawFieldRows.length > 0 ? exportRawFields : () => openQueryTab('fields')
    },
    {
      label: '客户说明',
      value: '可复制',
      detail: deliveryCoverRecommendation(fieldEvidenceCount, validLocations.length, rawFrames.total ?? 0, locations.total ?? 0),
      color: 'blue' as const,
      action: '复制说明',
      disabled: false,
      onClick: copyDeliveryPackage
    }
  ];
  const customerDeliveryConclusionItems = [
    {
      label: '交付状态',
      value: deliveryState,
      detail: deliveryState === '可交付' ? '当前范围可直接生成客户证据包。' : deliveryState === '待查询数据' ? '先查询位置或明细数据后再交付。' : '可交付，但需要附带复核说明。',
      color: deliveryStateColor,
      onClick: () => copyDeliveryPackage()
    },
    {
      label: '位置历史',
      value: `${locations.total.toLocaleString()} 条`,
      detail: `${validLocations.length.toLocaleString()} 个有效坐标，可用于轨迹复盘。`,
      color: locations.total > 0 ? 'green' as const : 'grey' as const,
      onClick: () => openQueryTab('location')
    },
    {
      label: '明细证据',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
      detail: '用于解释位置、里程、字段值和原始上报来源。',
      color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      onClick: () => openQueryTab('raw')
    },
    {
      label: '字段裁剪',
      value: `${fieldEvidenceCount.toLocaleString()} 字段`,
      detail: selectedFieldCount > 0 ? `已按 ${selectedFieldCount.toLocaleString()} 个字段裁剪。` : '按客户需要选择字段后再交付。',
      color: fieldEvidenceCount > 0 ? 'green' as const : 'orange' as const,
      onClick: () => openQueryTab('fields')
    }
  ];
  const customerDeliveryConclusionActions = [
    {
      label: '导出位置',
      action: '位置CSV',
      color: locations.items.length > 0 ? 'green' as const : 'grey' as const,
      disabled: locations.items.length === 0,
      onClick: exportLocations
    },
    {
      label: '导出明细',
      action: '明细CSV',
      color: rawFrames.items.length > 0 ? 'blue' as const : 'grey' as const,
      disabled: rawFrames.items.length === 0,
      onClick: exportRawFrames
    },
    {
      label: '字段裁剪',
      action: '字段CSV',
      color: rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
      disabled: rawFieldRows.length === 0,
      onClick: rawFieldRows.length > 0 ? exportRawFields : () => openQueryTab('fields')
    },
    {
      label: '复制说明',
      action: '交付包',
      color: deliveryStateColor,
      disabled: false,
      onClick: copyDeliveryPackage
    }
  ];
  const nextExportPrimary = rawFieldRows.length > 0
    ? '字段证据'
    : locations.items.length > 0
      ? '位置历史'
      : rawFrames.items.length > 0
        ? '明细证据'
        : '先查询';
  const runPrimaryExport = () => {
    if (rawFieldRows.length > 0) {
      exportRawFields();
      return;
    }
    if (locations.items.length > 0) {
      exportLocations();
      return;
    }
    if (rawFrames.items.length > 0) {
      exportRawFrames();
      return;
    }
    openQueryTab('raw');
  };
  const nextExportDecisionItems = [
    {
      label: '首要交付',
      value: nextExportPrimary,
      detail: rawFieldRows.length > 0 ? '优先交付客户选择的字段证据。' : locations.items.length > 0 ? '优先交付位置历史和轨迹复盘。' : rawFrames.items.length > 0 ? '先交付明细证据解释来源。' : '先查询车辆和时间窗数据。',
      color: rawFieldRows.length > 0 || locations.items.length > 0 ? 'green' as const : rawFrames.items.length > 0 ? 'blue' as const : 'orange' as const,
      onClick: runPrimaryExport
    },
    {
      label: '轨迹状态',
      value: validLocations.length > 0 ? `${validLocations.length.toLocaleString()} 点` : '待补轨迹',
      detail: validLocations.length > 0 ? '可以进入轨迹回放和位置导出。' : '当前时间窗没有有效坐标，建议补查位置历史。',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      onClick: () => openQueryTab('location')
    },
    {
      label: '证据状态',
      value: `${(rawFrames.total ?? 0).toLocaleString()} 帧 / ${fieldEvidenceCount.toLocaleString()} 字段`,
      detail: fieldEvidenceCount > 0 ? '字段证据已准备，可缩小导出体积。' : '可从明细证据中选择客户需要的字段。',
      color: fieldEvidenceCount > 0 ? 'green' as const : (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
      onClick: () => openQueryTab(fieldEvidenceCount > 0 ? 'fields' : 'raw')
    },
    {
      label: '说明状态',
      value: deliveryState,
      detail: anomalyTotal > 0 ? '复制时会带上质量提示和复核说明。' : '可复制客户可读的交付说明。',
      color: deliveryStateColor,
      onClick: copyDeliveryPackage
    }
  ];
  const nextExportActionItems = [
    {
      label: '导出优先项',
      action: '执行导出',
      color: nextExportPrimary === '字段证据' || nextExportPrimary === '位置历史' ? 'green' as const : nextExportPrimary === '明细证据' ? 'blue' as const : 'orange' as const,
      disabled: nextExportPrimary === '先查询',
      onClick: runPrimaryExport
    },
    {
      label: '补查轨迹',
      action: '位置历史',
      color: validLocations.length > 0 ? 'green' as const : 'orange' as const,
      disabled: false,
      onClick: () => openQueryTab('location')
    },
    {
      label: '复制说明',
      action: '交付包',
      color: deliveryStateColor,
      disabled: false,
      onClick: copyDeliveryPackage
    },
    {
      label: '保存视图',
      action: '复用',
      color: 'blue' as const,
      disabled: false,
      onClick: copySavedReportView
    }
  ];
  const tripReviewSummaryItems = [
    {
      label: '起点',
      value: firstLocation?.deviceTime || firstLocation?.serverTime || '-',
      detail: firstLocation ? `${formatNumber(firstLocation.longitude)}, ${formatNumber(firstLocation.latitude)}` : '暂无起点',
      color: firstLocation ? 'green' as const : 'grey' as const,
      disabled: !firstLocation,
      onClick: () => openQueryTab('location')
    },
    {
      label: '终点',
      value: lastLocation?.deviceTime || lastLocation?.serverTime || '-',
      detail: lastLocation ? `${formatNumber(lastLocation.longitude)}, ${formatNumber(lastLocation.latitude)}` : '暂无终点',
      color: lastLocation ? 'green' as const : 'grey' as const,
      disabled: !lastLocation,
      onClick: () => openQueryTab('location')
    },
    {
      label: '行程里程',
      value: formatNumber(mileageDelta, ' km'),
      detail: `回放跨度 ${formatDurationMinutes(playbackSpanMinutes)}，用于解释区间里程。`,
      color: isFiniteNumber(mileageDelta) ? 'blue' as const : 'grey' as const,
      disabled: !isFiniteNumber(mileageDelta),
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      label: '最高速度',
      value: formatNumber(maxSpeed, ' km/h'),
      detail: '用于复核超速、异常驾驶或位置跳点。',
      color: isFiniteNumber(maxSpeed) ? 'blue' as const : 'grey' as const,
      disabled: !isFiniteNumber(maxSpeed),
      onClick: () => openQueryTab('location')
    },
    {
      label: '异常提示',
      value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项` : '无明显异常',
      detail: `断点 ${trajectoryAnomalies.gapCount.toLocaleString()} / 回退 ${trajectoryAnomalies.mileageRollbackCount.toLocaleString()} / 超速 ${trajectoryAnomalies.overspeedCount.toLocaleString()}`,
      color: anomalyTotal > 0 ? 'orange' as const : 'green' as const,
      disabled: false,
      onClick: copyTrajectoryReviewPackage
    }
  ];
  const tripReviewActionItems = [
    {
      label: '轨迹回放',
      action: '播放轨迹',
      detail: `${validLocations.length.toLocaleString()} 个有效定位点`,
      color: validLocations.length > 0 ? 'green' as const : 'grey' as const,
      disabled: validLocations.length === 0,
      onClick: () => openQueryTab('location')
    },
    {
      label: '统计核对',
      action: '里程统计',
      detail: `区间里程 ${formatNumber(mileageDelta, ' km')}`,
      color: isFiniteNumber(mileageDelta) ? 'blue' as const : 'grey' as const,
      disabled: !onOpenMileage || !currentVehicleKeyword,
      onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })
    },
    {
      label: '导出证据',
      action: '复制交付',
      detail: `位置 ${locations.total.toLocaleString()} / 明细 ${(rawFrames.total ?? 0).toLocaleString()}`,
      color: deliveryStateColor,
      disabled: false,
      onClick: copyDeliveryPackage
    },
    {
      label: '告警复盘',
      action: '告警事件',
      detail: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项异常待说明` : '同一时间窗查看告警闭环',
      color: anomalyTotal > 0 ? 'orange' as const : 'blue' as const,
      disabled: false,
      onClick: () => {
        window.location.hash = buildAppHash({
          page: 'alert-events',
          keyword: currentVehicleKeyword,
          protocol: currentProtocol,
          filters: {
            ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}),
            ...(filters.dateTo ? { dateTo: filters.dateTo } : {})
          }
        });
      }
    }
  ];

  return (
    <div className="vp-page">
      <PageHeader
        title={pageTitle}
        description={pageDescription}
        actions={(
          <Space>
            <Button icon={<IconCopy />} onClick={copyHistoryEvidencePackage}>复制数据复核包</Button>
            <Button disabled={!currentVehicleKeyword} onClick={() => onOpenVehicle(currentVehicleKeyword, currentProtocol)}>
              当前车辆服务
            </Button>
          </Space>
        )}
      />
      <div className="vp-scope-bar">
        <span className="vp-scope-label">当前车辆：{currentVehicleKeyword || '-'}</span>
        <Tag color={currentProtocol ? 'blue' : 'green'}>来源证据：{currentProtocol || '全部来源证据'}</Tag>
        <Typography.Text type="tertiary">{scopeDescription}</Typography.Text>
      </div>
      {mode === 'query' ? (
        <section className="vp-history-export-wizard" aria-label="客户导出向导">
          <div className="vp-history-export-wizard-copy">
            <Space wrap>
              <Tag color="blue">客户导出向导</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={rawFieldRows.length > 0 ? 'green' : 'orange'}>{exportWizardEvidenceLabel}</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>
              客户导出向导
            </Typography.Title>
            <Typography.Text type="secondary">
              按客户交付顺序完成：先锁定车辆和时间窗，再选择轨迹、明细、字段或统计证据，最后复核质量并复制交付包。
            </Typography.Text>
            <Button size="small" theme="solid" type="primary" icon={<IconCopy />} onClick={copyExportWizardPackage}>复制导出向导包</Button>
          </div>
          <div className="vp-history-export-wizard-steps">
            {exportWizardSteps.map((item) => (
              <button
                key={item.step}
                type="button"
                className="vp-history-export-wizard-step"
                onClick={item.onClick}
                aria-label={`客户导出向导 ${item.step} ${item.title} ${item.value}`}
              >
                <Tag color={item.color}>{item.step}</Tag>
                <strong>{item.title}</strong>
                <span>{item.value}</span>
                <small>{item.detail}</small>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-delivery-conclusion-strip" aria-label="客户数据交付封面">
          <div className="vp-history-delivery-conclusion-copy">
            <Space wrap>
              <Tag color="blue">客户数据交付封面</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={fieldEvidenceCount > 0 ? 'green' : 'orange'}>{fieldEvidenceCount.toLocaleString()} 字段</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>
              先给客户一个可读封面：交付结论、车辆范围、时间窗、交付物、质量提示和下一步动作。
            </Typography.Title>
            <Typography.Text type="secondary">
              交付主题：{currentVehicleKeyword || '全部车辆'} / {currentProtocol || '全部来源证据'} / {evidenceDateFrom || '-'} 至 {evidenceDateTo || '-'}。
            </Typography.Text>
            <Space wrap>
              <Button size="small" theme="solid" type="primary" icon={<IconCopy />} onClick={copyDeliveryCover}>复制交付封面包</Button>
              <Button size="small" onClick={copyDeliveryPackage}>复制交付说明</Button>
            </Space>
          </div>
          <div className="vp-history-delivery-conclusion-metrics">
            {deliveryCoverItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-delivery-conclusion-metric"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户数据交付封面 ${item.label} ${item.value} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-delivery-conclusion-strip" aria-label="客户数据交付结论条">
          <div className="vp-history-delivery-conclusion-copy">
            <Space wrap>
              <Tag color="blue">客户数据交付结论条</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={fieldEvidenceCount > 0 ? 'green' : 'orange'}>{fieldEvidenceCount.toLocaleString()} 字段</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>
              先判断这次查询能交付什么，再选择导出位置、明细证据、字段裁剪或复制说明。
            </Typography.Title>
            <Typography.Text type="secondary">
              当前范围：{deliveryScopeText}，时间窗：{filters.dateFrom || '-'} 至 {filters.dateTo || '-'}。
            </Typography.Text>
          </div>
          <div className="vp-history-delivery-conclusion-metrics">
            {customerDeliveryConclusionItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-delivery-conclusion-metric"
                onClick={item.onClick}
                aria-label={`客户数据交付结论 ${item.label} ${item.value}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
          <div className="vp-history-delivery-conclusion-actions">
            {customerDeliveryConclusionActions.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-delivery-conclusion-action"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户数据交付动作 ${item.label} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <span>{item.action}</span>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-next-export-strip" aria-label="导出下一步行动条">
          <div className="vp-history-next-export-copy">
            <Space wrap>
              <Tag color="blue">导出下一步行动条</Tag>
              <Tag color={nextExportPrimary === '字段证据' || nextExportPrimary === '位置历史' ? 'green' : nextExportPrimary === '明细证据' ? 'blue' : 'orange'}>
                {nextExportPrimary}
              </Tag>
              <Tag color={validLocations.length > 0 ? 'green' : 'orange'}>
                {validLocations.length > 0 ? '轨迹可用' : '待补轨迹'}
              </Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>
              当前查询先决定交付什么、缺什么、怎么复用，避免客户拿到一堆无解释 CSV。
            </Typography.Title>
            <Typography.Text type="secondary">
              建议交付：{nextExportPrimary}；证据：{(rawFrames.total ?? 0).toLocaleString()} 帧 / {fieldEvidenceCount.toLocaleString()} 字段；范围：{deliveryScopeText}。
            </Typography.Text>
          </div>
          <div className="vp-history-next-export-grid">
            {nextExportDecisionItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-next-export-item"
                onClick={item.onClick}
                aria-label={`导出下一步行动 ${item.label} ${item.value}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
          <div className="vp-history-next-export-actions">
            {nextExportActionItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-next-export-action"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`导出下一步动作 ${item.label} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <span>{item.action}</span>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      <section className="vp-history-evidence-chain" aria-label="客户时间窗证据链">
        <div className="vp-history-evidence-chain-summary">
          <Space wrap>
            <Tag color="blue">客户时间窗证据链</Tag>
            <Tag color={deliveryStateColor}>{deliveryState}</Tag>
            <Tag color={currentVehicleKeyword ? 'green' : 'orange'}>{currentVehicleKeyword || '待选择车辆'}</Tag>
          </Space>
          <Typography.Title heading={5} style={{ margin: 0 }}>
            同一辆车和同一时间窗，串起实时轨迹、里程核对、历史导出、字段裁剪和告警复盘。
          </Typography.Title>
          <Typography.Text type="secondary">
            接入来源只是证据来源；客户页面只回答车辆在哪里、跑了多少、能否导出、异常谁处理。
          </Typography.Text>
        </div>
        <div className="vp-history-evidence-chain-grid">
          {customerTimeWindowEvidenceItems.map((item) => (
            <button
              key={item.title}
              type="button"
              className="vp-history-evidence-chain-item"
              disabled={item.disabled}
              onClick={item.onClick}
              aria-label={`客户时间窗证据链 ${item.title} ${item.action}`}
            >
              <Tag color={item.color}>{item.title}</Tag>
              <strong>{item.value}</strong>
              <span>{item.detail}</span>
              <em>{item.action}</em>
            </button>
          ))}
        </div>
      </section>
      {mode === 'trajectory' ? (
        <section className="vp-trip-review-summary" aria-label="行程复盘摘要">
          <div className="vp-trip-review-summary-copy">
            <Space wrap>
              <Tag color="blue">行程复盘摘要</Tag>
              <Tag color={trajectoryDecisionColor}>{trajectoryDecisionState}</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>
              把客户最关心的起止时间、路线跨度、里程差、最高速度和异常提示放在轨迹前面。
            </Typography.Title>
            <Typography.Text type="secondary">
              这层摘要对应 Trip History 的客户读法：先给结论，再让客户选择回放、统计、导出或告警复盘。
            </Typography.Text>
          </div>
          <div className="vp-trip-review-summary-grid">
            {tripReviewSummaryItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-trip-review-summary-item"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`行程复盘摘要 ${item.label} ${item.value}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
          <div className="vp-trip-review-action-grid">
            {tripReviewActionItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-trip-review-action"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`行程复盘动作 ${item.label} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.action}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-evidence-package-overview" aria-label="客户证据包交付总览">
          <div className="vp-history-evidence-package-summary">
            <Space wrap>
              <Tag color="blue">客户证据包交付总览</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={fieldEvidenceCount > 0 ? 'green' : 'orange'}>{fieldEvidenceCount.toLocaleString()} 字段</Tag>
            </Space>
            <Typography.Text strong>先给客户一个结论：当前筛选能交付哪些证据、还缺哪一类证据、下一步应该导出还是补查。</Typography.Text>
          </div>
          <div className="vp-history-evidence-package-grid">
            {customerEvidencePackageOverviewItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-evidence-package-item"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户证据包交付总览 ${item.label} ${item.value} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-delivery-readiness" aria-label="客户交付准备度">
          <div className="vp-history-delivery-readiness-copy">
            <Space wrap>
              <Tag color="blue">客户交付准备度</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
            </Space>
            <Typography.Text strong>
              把一次历史查询收敛成四个交付判断：范围是否锁定、轨迹是否可用、字段是否裁剪、说明是否可复制。
            </Typography.Text>
          </div>
          <div className="vp-history-delivery-readiness-grid">
            {customerDeliveryReadinessItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-delivery-readiness-item"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户交付准备度 ${item.label} ${item.value} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-export-acceptance" aria-label="客户导出验收清单">
          <div className="vp-history-export-acceptance-copy">
            <Space wrap>
              <Tag color="blue">客户导出验收清单</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={currentVehicleKeyword ? 'green' : 'orange'}>{currentVehicleKeyword || '全部车辆'}</Tag>
            </Space>
            <Typography.Text strong>
              客户交付前只看五个门禁：范围、轨迹、明细、字段、异常说明；任何一项不满足都能直接进入补查或导出。
            </Typography.Text>
          </div>
          <div className="vp-history-export-acceptance-grid">
            {exportAcceptanceItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-export-acceptance-item"
                onClick={item.onClick}
                aria-label={`客户导出验收 ${item.label} ${item.value}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
          <div className="vp-history-export-acceptance-actions">
            {exportAcceptanceActions.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-export-acceptance-action"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`导出验收动作 ${item.label} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.action}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-service-desk" aria-label="车辆历史服务台">
          <div className="vp-history-service-desk-summary">
            <Space wrap>
              <Tag color="blue">车辆历史服务台</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={currentVehicleKeyword ? 'green' : 'orange'}>{currentVehicleKeyword || '全部车辆'}</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>
              客户先选车辆和时间窗，再拿到路线、里程、原始证据、字段裁剪和告警说明。
            </Typography.Title>
            <div className="vp-history-service-desk-meta">
              <span>当前服务范围：{currentVehicleKeyword || '全部车辆'} / {currentProtocol || '全部来源证据'}</span>
              <span>时间窗：{filters.dateFrom || '-'} 至 {filters.dateTo || '-'}</span>
              <span>协议来源只作为证据过滤，不作为客户主流程。</span>
            </div>
          </div>
          <div className="vp-history-service-desk-grid">
            {vehicleHistoryServiceItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-history-service-desk-item"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`车辆历史服务台 ${item.label} ${item.value} ${item.action}`}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <section className="vp-history-export-center" aria-label="客户导出中心">
          <div className="vp-history-export-center-summary">
            <Space wrap>
              <Tag color="blue">客户导出中心</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={currentVehicleKeyword ? 'green' : 'orange'}>{currentVehicleKeyword || '全部车辆'}</Tag>
            </Space>
            <strong>按客户用途选择报表：行程历史、里程对账、字段证据和告警说明。</strong>
            <span>客户要的是可交付报表和复盘说明；原始记录、字段裁剪和协议来源只作为证据层。</span>
          </div>
          <div className="vp-history-export-center-grid">
            {customerExportCenterItems.map((item) => (
              <button
                key={item.title}
                type="button"
                className="vp-history-export-center-item"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户导出中心 ${item.title} ${item.value} ${item.action}`}
              >
                <Tag color={item.color}>{item.title}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </section>
      ) : null}
      {mode === 'query' ? (
        <Card bordered title="自定义时间窗监控台" style={{ marginTop: 16 }}>
          <div className="vp-history-time-window-board">
            <div className="vp-history-time-window-summary">
              <Space wrap>
                <Tag color="blue">自定义时间窗</Tag>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={currentVehicleKeyword ? 'green' : 'orange'}>{currentVehicleKeyword || '全部车辆'}</Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>客户选定车辆和时间范围后，直接判断这个窗口能否用于轨迹复盘、统计核对、历史证据导出和告警说明。</Typography.Title>
              <Typography.Text type="secondary">
                时间窗是客户查询的主线：同一组筛选条件必须贯穿轨迹、里程、历史明细、字段裁剪和告警复盘，避免各页面口径不一致。
              </Typography.Text>
            </div>
            <div className="vp-history-time-window-grid">
              {timeWindowMonitorItems.map((item) => (
                <button
                  key={item.title}
                  type="button"
                  className="vp-history-time-window-item"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`自定义时间窗监控 ${item.title} ${item.action}`}
                >
                  <Tag color={item.color}>{item.title}</Tag>
                  <strong>{item.value}</strong>
                  <span>{item.detail}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card bordered className="vp-history-customer-export-board" bodyStyle={{ padding: 0 }}>
          <div className="vp-history-customer-export-summary">
            <Space wrap>
              <Tag color="blue">客户数据交付台</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={activeTab === 'location' ? 'green' : activeTab === 'raw' ? 'blue' : 'orange'}>
                {activeTab === 'location' ? '位置历史' : activeTab === 'raw' ? '明细证据' : '字段裁剪'}
              </Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>先锁定车辆和时间窗，再选择交付物并导出</Typography.Title>
            <Typography.Text type="secondary">
              面向客户的数据导出以车辆服务为中心：同一个筛选范围可以交付轨迹报告、位置历史、明细证据、字段裁剪、CSV 文件和里程统计。
            </Typography.Text>
            <Space wrap>
              <Button size="small" theme="solid" type="primary" onClick={() => openQueryTab('location')}>查询位置</Button>
              <Button size="small" theme="light" type="primary" onClick={() => openQueryTab('raw')}>明细证据</Button>
              <Button size="small" theme="light" type="primary" onClick={() => openQueryTab('fields')}>字段裁剪</Button>
              <Button size="small" theme="light" onClick={copyDeliveryPackage}>复制交付包</Button>
            </Space>
          </div>
          <div className="vp-history-customer-export-steps">
            {[
              {
                step: '01',
                title: '选择范围',
                value: currentVehicleKeyword || '全部车辆',
                detail: `${currentProtocol || '全部来源证据'} / ${filters.dateFrom || '-'} 至 ${filters.dateTo || '-'}`,
                action: '调整筛选',
                color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
                disabled: false,
                onClick: () => document.querySelector<HTMLInputElement>('input[name="keyword"]')?.focus()
              },
              {
                step: '02',
                title: '位置历史',
                value: `${locations.total.toLocaleString()} 条`,
                detail: `${validLocations.length.toLocaleString()} 个有效坐标，可用于定位复盘和轨迹回放。`,
                action: '导出位置',
                color: locations.total > 0 ? 'green' as const : 'grey' as const,
                disabled: locations.items.length === 0,
                onClick: exportLocations
              },
              {
                step: '03',
                title: '明细证据',
                value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
                detail: '保留接入证据，用于解释位置、里程和字段来源。',
                action: '导出证据',
                color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
                disabled: rawFrames.items.length === 0,
                onClick: exportRawFrames
              },
              {
                step: '04',
                title: '字段裁剪',
                value: `${rawFieldRows.length.toLocaleString()} 个`,
                detail: selectedFieldCount > 0 ? `已裁剪 ${selectedFieldCount.toLocaleString()} 个字段。` : '需要字段级交付时切换字段裁剪或填写字段裁剪。',
                action: '字段配置',
                color: rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
                disabled: false,
                onClick: () => openQueryTab('fields')
              },
              {
                step: '05',
                title: '交付说明',
                value: deliveryState,
                detail: anomalyTotal > 0 ? `存在 ${anomalyTotal.toLocaleString()} 项质量提示，交付时需说明。` : '当前范围未发现明显轨迹质量提示。',
                action: '复制交付',
                color: deliveryStateColor,
                disabled: false,
                onClick: copyDeliveryPackage
              }
            ].map((item) => (
              <button
                key={item.step}
                type="button"
                className="vp-history-customer-export-step"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户数据交付台 ${item.title} ${item.action}`}
              >
                <span>{item.step}</span>
                <Tag color={item.color}>{item.title}</Tag>
                <strong>{item.value}</strong>
                <small>{item.detail}</small>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card bordered title="报表交付模板" style={{ marginTop: 16 }}>
          <div className="vp-history-report-templates">
            <div className="vp-history-report-summary">
              <Space wrap>
                <Tag color="blue">报告模板</Tag>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={selectedFieldCount > 0 ? 'green' : 'grey'}>
                  {selectedFieldCount > 0 ? `${selectedFieldCount.toLocaleString()} 个字段裁剪` : '未裁剪字段'}
                </Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>按客户问题选择导出模板</Typography.Title>
              <Typography.Text type="secondary">
                车队报表不只是下载 CSV：先选择用途，再生成轨迹报告、明细证据包、字段裁剪包或周期交付计划。
              </Typography.Text>
            </div>
            <div className="vp-history-report-template-grid">
              {historyReportTemplates.map((item) => (
                <button
                  key={item.title}
                  type="button"
                  className="vp-history-report-template"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`报表交付模板 ${item.title} ${item.action}`}
                >
                  <Tag color={item.color}>{item.title}</Tag>
                  <strong>{item.value}</strong>
                  <span>{item.detail}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card bordered title="客户报表交付节奏" style={{ marginTop: 16 }}>
          <div className="vp-history-report-purpose" aria-label="客户报表用途导航">
            <div className="vp-history-report-purpose-summary">
              <Space wrap>
                <Tag color="blue">客户报表用途导航</Tag>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={currentVehicleKeyword ? 'green' : 'grey'}>{currentVehicleKeyword ? '单车范围' : '全部车辆'}</Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>先选择客户要解决的问题，再决定导出位置、字段、统计或说明。</Typography.Title>
              <Typography.Text type="secondary">
                报表入口按客户用途组织：日报复盘、周报对账、临时问题解释和字段模板复用，避免客户先面对 RAW 或字段明细。
              </Typography.Text>
            </div>
            <div className="vp-history-report-purpose-grid">
              {customerReportPurposeItems.map((item) => (
                <button
                  key={item.title}
                  type="button"
                  className="vp-history-report-purpose-item"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`客户报表用途导航 ${item.title} ${item.value} ${item.action}`}
                >
                  <Tag color={item.color}>{item.title}</Tag>
                  <strong>{item.value}</strong>
                  <span>{item.detail}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
          <div className="vp-history-report-cadence">
            <div className="vp-history-report-cadence-summary">
              <Space wrap>
                <Tag color="blue">报表节奏</Tag>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={currentVehicleKeyword ? 'green' : 'grey'}>{currentVehicleKeyword ? '单车交付' : '批量交付'}</Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>把现代车队平台的报表能力落到客户交付：日报复盘、周报对账、临时解释和字段模板复用。</Typography.Title>
              <Typography.Text type="secondary">
                日报用于客户复盘，周报用于客户对账，临时导出用于问题解释，字段模板用于稳定复用。
              </Typography.Text>
            </div>
            <div className="vp-history-report-cadence-grid">
              {customerReportCadenceItems.map((item) => (
                <button
                  key={item.title}
                  type="button"
                  className="vp-history-report-cadence-item"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`客户报表交付节奏 ${item.title} ${item.action}`}
                >
                  <Tag color={item.color}>{item.title}</Tag>
                  <strong>{item.value}</strong>
                  <span>{item.detail}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
          <div className="vp-history-export-template-library">
            <div className="vp-history-export-template-library-summary">
              <Space wrap>
                <Tag color="blue">客户导出包模板库</Tag>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' : 'orange'}>
                  {fieldEvidenceCount.toLocaleString()} 字段
                </Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>把一次性 CSV 下载变成客户可复用的报表模板：日报看车辆、周报做对账、临时解释处理质疑、字段模板保持口径稳定。</Typography.Title>
              <Typography.Text type="secondary">
                每个模板都绑定同一辆车、同一时间窗和同一套证据口径，客户后续拿到的是稳定报表，而不是临时拼出来的查询结果。
              </Typography.Text>
            </div>
            <div className="vp-history-export-template-library-grid">
              {customerExportTemplateLibraryItems.map((item) => (
                <button
                  key={item.title}
                  type="button"
                  className="vp-history-export-template-library-item"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`客户导出包模板库 ${item.title} ${item.value} ${item.action}`}
                >
                  <Tag color={item.color}>{item.title}</Tag>
                  <strong>{item.value}</strong>
                  <span>{item.detail}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card bordered title="保存报表视图" style={{ marginTop: 16 }}>
          <div className="vp-history-saved-report">
            <div className="vp-history-saved-report-summary">
              <Space wrap>
                <Tag color="blue">可复用视图</Tag>
                <Tag color={currentVehicleKeyword ? 'green' : 'grey'}>{currentVehicleKeyword ? '单车范围' : '全部车辆'}</Tag>
                <Tag color={selectedFieldCount > 0 || rawFieldRows.length > 0 ? 'green' : 'orange'}>
                  {selectedFieldCount > 0 ? `${selectedFieldCount.toLocaleString()} 个字段` : `${rawFieldRows.length.toLocaleString()} 个字段`}
                </Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>把当前车辆、时间窗、来源证据、字段裁剪和交付节奏保存成客户可复用视图，后续不用重新拼查询条件。</Typography.Title>
              <Typography.Text type="secondary">
                保存视图是前端可复制的报表配置：后续可升级为账号级报表模板、定时邮件或客户分组权限。
              </Typography.Text>
            </div>
            <div className="vp-history-saved-report-grid">
              {savedReportViewItems.map((item) => (
                <button
                  key={item.title}
                  type="button"
                  className="vp-history-saved-report-item"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`保存报表视图 ${item.title} ${item.value} ${item.action}`}
                >
                  <Tag color={item.color}>{item.title}</Tag>
                  <strong>{item.value}</strong>
                  <span>{item.detail}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card bordered title="客户历史问题" style={{ marginTop: 16 }}>
          <div className="vp-history-question-board">
            <div className="vp-history-question-summary">
              <Space wrap>
                <Tag color="blue">客户问题</Tag>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={currentVehicleKeyword ? 'green' : 'orange'}>{currentVehicleKeyword ? '单车范围' : '全部车辆'}</Tag>
              </Space>
              <Typography.Title heading={5} style={{ margin: 0 }}>用客户语言回答路线、里程、异常和导出问题</Typography.Title>
              <Typography.Text type="secondary">
                查询页不要求客户理解协议和字段表，先把问题转成轨迹、统计、证据和交付包，再进入对应明细。
              </Typography.Text>
            </div>
            <div className="vp-history-question-grid">
              {customerHistoryQuestions.map((item) => (
                <button
                  key={item.question}
                  type="button"
                  className="vp-history-question-item"
                  disabled={item.disabled}
                  onClick={item.onClick}
                  aria-label={`客户历史问题 ${item.question} ${item.action}`}
                >
                  <Tag color={item.color}>{item.question}</Tag>
                  <strong>{item.answer}</strong>
                  <span>{item.evidence}</span>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      <Card bordered title="客户复盘导航" style={{ marginTop: 16 }}>
        <div className="vp-history-replay-nav">
          <div className="vp-history-replay-nav-summary">
            <Space wrap>
              <Tag color="blue">Trips History</Tag>
              <Tag color={trajectoryDecisionColor}>{trajectoryDecisionState}</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>像 Trips History 一样，先锁定车辆和时间，再回放路线、核对里程、导出证据。</Typography.Title>
            <Typography.Text type="secondary">
              客户复盘不从 RAW 开始，而是从车辆、时间、路线和交付结果开始；字段和原始记录只作为证明材料。
            </Typography.Text>
          </div>
          <div className="vp-history-replay-nav-grid">
            {customerReplayNavigation.map((item) => (
              <button
                key={item.title}
                type="button"
                className="vp-history-replay-nav-item"
                disabled={item.disabled}
                onClick={item.onClick}
                aria-label={`客户复盘导航 ${item.title} ${item.action}`}
              >
                <Tag color={item.color}>{item.title}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </div>
      </Card>
      <Card bordered className="vp-trip-workbench" bodyStyle={{ padding: 0 }}>
        <div className="vp-trip-workbench-map">
          <div className="vp-trip-workbench-copy">
            <Space wrap>
              <Tag color="blue">轨迹回放工作台</Tag>
              <Tag color={trajectoryDecisionColor}>{trajectoryDecisionState}</Tag>
              <Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
            </Space>
            <Typography.Title heading={3} style={{ margin: 0 }}>按车辆和时间窗复盘每一段行程</Typography.Title>
            <Typography.Text type="secondary">
              客户需要看到路线、起止时间、里程变化、速度异常和可导出的证据。明细证据和字段裁剪只作为轨迹复盘的依据。
            </Typography.Text>
          </div>
          <VehicleMap
            points={playbackPoints}
            mode="track"
            selectedId={currentPlaybackPointId}
            onPointSelect={selectPlaybackMapPoint}
            heightClassName="vp-trip-workbench-map-canvas"
            fallbackLabel="显示轨迹坐标预览"
          />
          <div className="vp-trip-workbench-facts">
            {[
              { label: '轨迹点', value: locations.total.toLocaleString(), detail: `${validLocations.length.toLocaleString()} 个有效坐标`, color: 'blue' as const },
              { label: '行程里程', value: formatNumber(mileageDelta, ' km'), detail: `最高速度 ${formatNumber(maxSpeed, ' km/h')}`, color: 'green' as const },
              { label: '回放跨度', value: formatDurationMinutes(playbackSpanMinutes), detail: `采样 ${formatDurationMinutes(playbackIntervalMinutes, '/点')}`, color: 'blue' as const },
              { label: '异常提示', value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项` : '无明显异常', detail: `断点 ${trajectoryAnomalies.gapCount.toLocaleString()} / 回退 ${trajectoryAnomalies.mileageRollbackCount.toLocaleString()} / 超速 ${trajectoryAnomalies.overspeedCount.toLocaleString()}`, color: anomalyTotal > 0 ? 'orange' as const : 'green' as const }
            ].map((item) => (
              <button key={item.label} type="button" className="vp-trip-workbench-fact" onClick={item.label === '异常提示' ? copyTrajectoryReviewPackage : () => openQueryTab('location')} aria-label={`轨迹回放指标 ${item.label} ${item.value}`}>
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
              </button>
            ))}
          </div>
        </div>
        <div className="vp-trip-workbench-side">
          <div className="vp-trip-current-card">
            <Space wrap>
              <Tag color={currentPlayback ? 'green' : 'grey'}>{currentPlayback ? `点 ${currentPlaybackIndex + 1}/${playbackRows.length}` : '暂无回放点'}</Tag>
              <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol ? `来源证据 ${currentProtocol}` : '全部来源证据'}</Tag>
            </Space>
            <Typography.Text strong>{currentPlayback?.plate || (currentPlayback ? '当前回放车辆' : currentVehicleKeyword ? '当前查询车辆' : '全部车辆')}</Typography.Text>
            <Typography.Text type="secondary">{currentPlayback?.deviceTime || currentPlayback?.serverTime || '请选择车辆和时间窗查询轨迹'}</Typography.Text>
            <div className="vp-trip-current-metrics">
              <span>速度 <strong>{formatNumber(currentPlayback?.speedKmh, ' km/h')}</strong></span>
              <span>总里程 <strong>{formatNumber(currentPlayback?.totalMileageKm, ' km')}</strong></span>
            </div>
            <Typography.Text type="tertiary" ellipsis={{ showTooltip: true }}>
              {currentPlaybackAddress?.formattedAddress || (currentPlayback ? `${currentPlayback.longitude}, ${currentPlayback.latitude}` : '暂无坐标')}
            </Typography.Text>
            <Space wrap>
              <Button size="small" theme="solid" type="primary" disabled={playbackRows.length <= 1} onClick={togglePlayback}>{playbackPlaying ? '暂停' : playbackAtEnd ? '重播' : '播放'}</Button>
              <Button size="small" disabled={currentPlaybackIndex <= 0} onClick={() => movePlayback(-1)}>上一点</Button>
              <Button size="small" disabled={currentPlaybackIndex >= playbackRows.length - 1} onClick={() => movePlayback(1)}>下一点</Button>
              <Button size="small" loading={reverseGeocoding} disabled={!currentPlayback || !hasValidCoordinate(currentPlayback)} onClick={resolveCurrentPlaybackAddress}>解析地址</Button>
            </Space>
          </div>
          <div className="vp-trip-actions">
            {[
              { title: '轨迹复盘', value: validLocations.length > 0 ? '可播放' : '待查询', detail: '回放路线、速度和里程断点。', action: '播放轨迹', color: validLocations.length > 0 ? 'green' as const : 'orange' as const, disabled: validLocations.length === 0, onClick: () => openQueryTab('location') },
              { title: '明细证据', value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`, detail: '查看轨迹背后的接入证据。', action: '查看证据', color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const, disabled: false, onClick: () => openQueryTab('raw') },
              { title: '里程统计', value: formatNumber(mileageDelta, ' km'), detail: '进入同一时间窗的里程统计。', action: '里程复核', color: isFiniteNumber(mileageDelta) ? 'green' as const : 'grey' as const, disabled: !onOpenMileage, onClick: () => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) }) },
              { title: '客户导出', value: deliveryState, detail: '复制交付说明或导出 CSV 证据。', action: '复制交付', color: deliveryStateColor, disabled: false, onClick: copyDeliveryPackage }
            ].map((item) => (
              <button key={item.title} type="button" className="vp-trip-action" disabled={item.disabled} onClick={item.onClick} aria-label={`轨迹回放工作台 ${item.title} ${item.action}`}>
                <Tag color={item.color}>{item.title}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </div>
      </Card>
      <Card
        bordered
        title={<Space><span>客户轨迹决策台</span><Button size="small" icon={<IconCopy />} onClick={copyTrajectoryDecision}>复制决策说明</Button></Space>}
        style={{ marginTop: 16 }}
      >
        <div className="vp-trajectory-decision-board">
          <div className="vp-trajectory-decision-summary">
            <Space wrap>
              <Tag color={trajectoryDecisionColor}>{trajectoryDecisionState}</Tag>
              <Tag color={deliveryStateColor}>{deliveryState}</Tag>
              <Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图可用' : '坐标预览'}</Tag>
            </Space>
            <Typography.Title heading={5} style={{ margin: 0 }}>先判断轨迹能否复盘，再进入证据、字段和里程统计</Typography.Title>
            <Typography.Text type="secondary">
              客户问某辆车某段时间发生了什么时，先看定位覆盖、时间断点、里程速度和明细证据，避免把内部排查过程当成客户主流程。
            </Typography.Text>
            <Space wrap>
              <Button size="small" theme="solid" type="primary" icon={<IconCopy />} onClick={copyTrajectoryDecision}>复制决策说明</Button>
              <Button size="small" disabled={validLocations.length === 0} onClick={() => openQueryTab('location')}>播放轨迹</Button>
              <Button size="small" disabled={(rawFrames.total ?? 0) === 0} onClick={() => openQueryTab('raw')}>明细证据</Button>
              <Button size="small" disabled={!onOpenMileage} onClick={() => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })}>里程统计</Button>
            </Space>
          </div>
          <div className="vp-trajectory-decision-grid">
            {trajectoryDecisionItems.map((item) => (
              <button
                key={item.label}
                type="button"
                className="vp-trajectory-decision-item"
                disabled={item.disabled}
                aria-label={`客户轨迹决策 ${item.label} ${item.action}`}
                onClick={item.onClick}
              >
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </div>
      </Card>
      {mode === 'query' ? (
        <Card bordered title="历史查询与导出工作台" style={{ marginTop: 16 }}>
          <div className="vp-history-query-workbench">
            <div className="vp-history-query-summary">
              <Space wrap>
                <Tag color="blue">客户查询</Tag>
                <Tag color={activeTab === 'location' ? 'green' : 'blue'}>
                  {activeTab === 'location' ? '位置历史' : activeTab === 'raw' ? '明细证据' : '字段裁剪'}
                </Tag>
              </Space>
              <Typography.Text strong>{currentVehicleKeyword || '全部车辆'}</Typography.Text>
              <Typography.Text type="secondary">
                面向车辆服务的历史数据检索入口。先按车辆和时间缩小范围，再导出位置、明细证据或字段裁剪，用于客户问询、BI 核对和问题追溯。
              </Typography.Text>
              <Typography.Text type="tertiary">
                默认查询最近一天，不预置任何车辆；输入 VIN、车牌或手机号后进入单车查询导出。
              </Typography.Text>
              <Space wrap>
                <Button size="small" theme="solid" type="primary" onClick={() => openQueryTab('location')}>查询位置历史</Button>
                <Button size="small" onClick={() => openQueryTab('raw')}>查询明细证据</Button>
                <Button size="small" onClick={() => openQueryTab('fields')}>查询字段裁剪</Button>
              </Space>
            </div>
            <div className="vp-history-query-grid">
              {[
                {
                  label: '位置记录',
                  value: locations.total.toLocaleString(),
                  detail: `${validLocations.length.toLocaleString()} 个有效坐标，可用于轨迹和定位复盘。`,
                  action: '导出位置',
                  color: 'green' as const,
                  onClick: exportLocations
                },
                {
                  label: '明细证据',
                  value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
                  detail: '保存接入后的证据明细，可按车辆和时间追溯。',
                  action: '导出证据',
                  color: 'blue' as const,
                  onClick: exportRawFrames
                },
                {
                  label: '字段裁剪',
                  value: rawFieldRows.length.toLocaleString(),
                  detail: selectedFieldCount > 0 ? `已裁剪 ${selectedFieldCount.toLocaleString()} 个字段。` : '可勾选字段裁剪或填写字段裁剪后查询。',
                  action: '导出字段',
                  color: rawFieldRows.length > 0 ? 'green' as const : 'grey' as const,
                  onClick: exportRawFields
                },
                {
                  label: '服务联动',
                  value: currentVehicleKeyword ? '单车' : '批量',
                  detail: currentVehicleKeyword ? '可进入车辆服务、轨迹回放和里程核对。' : '输入 VIN、车牌或手机号后可进入单车服务。',
                  action: '车辆服务',
                  color: currentVehicleKeyword ? 'blue' as const : 'grey' as const,
                  onClick: () => currentVehicleKeyword && onOpenVehicle(currentVehicleKeyword, currentProtocol)
                }
              ].map((item) => (
                <div key={item.label} className="vp-history-query-item">
                  <Tag color={item.color}>{item.label}</Tag>
                  <strong>{item.value}</strong>
                  <Typography.Text type="secondary">{item.detail}</Typography.Text>
                  <Button size="small" disabled={item.label === '服务联动' && !currentVehicleKeyword} onClick={item.onClick}>{item.action}</Button>
                </div>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card bordered title="客户查询导出包" style={{ marginTop: 16 }}>
          <div className="vp-history-package-board">
            <div className="vp-history-package-summary">
              <Space wrap>
                <Tag color={deliveryStateColor}>{deliveryState}</Tag>
                <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部来源证据'}</Tag>
              </Space>
              <Typography.Text strong>{deliveryScopeText}</Typography.Text>
              <Typography.Text type="secondary">
                面向客户交付时，先确认车辆、时间、交付物和质量提示，再导出位置、明细证据或字段裁剪，避免把内部排查过程直接暴露给客户。
              </Typography.Text>
              <Space wrap>
                <Button size="small" theme="solid" type="primary" icon={<IconCopy />} onClick={copyDeliveryPackage}>
                  复制交付包
                </Button>
                <Button size="small" disabled={!currentVehicleKeyword} onClick={() => onOpenVehicle(currentVehicleKeyword, currentProtocol)}>
                  车辆服务
                </Button>
                <Button size="small" disabled={!onOpenMileage} onClick={() => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })}>
                  里程统计
                </Button>
              </Space>
            </div>
            <div className="vp-history-package-grid">
              {[
                {
                  label: '位置历史',
                  value: `${locations.total.toLocaleString()} 条`,
                  detail: `${validLocations.length.toLocaleString()} 个有效坐标，覆盖率 ${formatPercent(trajectoryCoverageRate)}。`,
                  color: locations.total > 0 ? 'green' as const : 'grey' as const,
                  action: '导出位置',
                  disabled: locations.items.length === 0,
                  onClick: exportLocations
                },
                {
                  label: '明细证据',
                  value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
                  detail: '用于追溯数据来自接入协议解析后的真实证据。',
                  color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
                  action: '导出证据',
                  disabled: rawFrames.items.length === 0,
                  onClick: exportRawFrames
                },
                {
                  label: '字段裁剪',
                  value: `${rawFieldRows.length.toLocaleString()} 个`,
                  detail: selectedFieldCount > 0 ? `按 ${selectedFieldCount.toLocaleString()} 个字段裁剪。` : '需要字段级交付时切换字段裁剪。',
                  color: rawFieldRows.length > 0 ? 'green' as const : 'orange' as const,
                  action: '字段配置',
                  disabled: false,
                  onClick: () => openQueryTab('fields')
                },
                {
                  label: '质量提示',
                  value: anomalyTotal > 0 ? `${anomalyTotal.toLocaleString()} 项` : '无明显异常',
                  detail: anomalyTotal > 0
                    ? `断点 ${trajectoryAnomalies.gapCount.toLocaleString()} / 里程回退 ${trajectoryAnomalies.mileageRollbackCount.toLocaleString()} / 超速 ${trajectoryAnomalies.overspeedCount.toLocaleString()}。`
                    : '当前页未发现断点、里程回退或超速。',
                  color: anomalyTotal > 0 ? 'orange' as const : 'green' as const,
                  action: '复制说明',
                  disabled: false,
                  onClick: copyDeliveryPackage
                }
              ].map((item) => (
                <button
                  key={item.label}
                  type="button"
                  className="vp-history-package-item"
                  disabled={item.disabled}
                  aria-label={`客户查询导出 ${item.label} ${item.action}`}
                  onClick={item.onClick}
                >
                  <span>
                    <Tag color={item.color}>{item.label}</Tag>
                    <strong>{item.value}</strong>
                  </span>
                  <Typography.Text type="secondary">{item.detail}</Typography.Text>
                  <em>{item.action}</em>
                </button>
              ))}
            </div>
          </div>
        </Card>
      ) : null}
      {mode === 'query' ? (
        <Card
          bordered
          title={<Space><span>客户交付物清单</span><Button size="small" icon={<IconCopy />} onClick={copyDeliveryPackage}>复制交付清单</Button></Space>}
          style={{ marginTop: 16 }}
        >
          <div className="vp-history-checklist-grid">
            {deliveryChecklistItems.map((item) => (
              <button
                key={item.title}
                type="button"
                className="vp-history-checklist-item"
                disabled={item.disabled}
                aria-label={`客户交付清单 ${item.title} ${item.action}`}
                onClick={item.onClick}
              >
                <Tag color={item.color}>{item.title}</Tag>
                <strong>{item.value}</strong>
                <span>{item.detail}</span>
                <em>{item.action}</em>
              </button>
            ))}
          </div>
        </Card>
      ) : null}
      <Card bordered>
        <Form key={JSON.stringify(filters)} initValues={filters} layout="horizontal" onSubmit={(values) => submit(values)}>
          <Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号" style={{ width: 260 }} />
          <Form.Select field="protocol" label="来源证据筛选" placeholder="全部来源证据" style={{ width: 190 }}>
            <Select.Option value="GB32960">GB32960</Select.Option>
            <Select.Option value="JT808">JT808</Select.Option>
            <Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option>
          </Form.Select>
          <Form.Input field="dateFrom" label="开始时间" placeholder="2026-07-03 00:00:00" style={{ width: 210 }} />
          <Form.Input field="dateTo" label="结束时间" placeholder="2026-07-03 23:59:59" style={{ width: 210 }} />
          <Form.Checkbox field="includeFields" noLabel>
            返回字段明细
          </Form.Checkbox>
          <Form.TextArea
            field="fields"
            label="字段裁剪"
            placeholder="可选，逗号分隔，如 jt808.header.phone,jt808.location.longitude"
            autosize={{ minRows: 1, maxRows: 3 }}
            style={{ width: 420 }}
          />
          <Space align="start">
            <Button icon={<IconSearch />} htmlType="submit" theme="solid" type="primary">查询</Button>
            <Button icon={<IconRefresh />} onClick={reset}>重置</Button>
          </Space>
        </Form>
      </Card>
      {filterSummary.length > 0 ? (
        <Card bordered title="当前历史筛选" style={{ marginTop: 16 }}>
          <Space wrap>
            {filterSummary.map((item) => (
              <Tag key={item} color="blue">{item}</Tag>
            ))}
            <Button size="small" onClick={clearRangeFilters}>清空筛选</Button>
          </Space>
        </Card>
      ) : null}
      <Card bordered title="历史查询导出任务板" style={{ marginTop: 16 }}>
        <div className="vp-history-delivery-grid">
          {[
            {
              title: '轨迹回放',
              tag: validLocations.length > 0 ? '可回放' : '待查询',
              tagColor: validLocations.length > 0 ? 'green' as const : 'orange' as const,
              value: `${validLocations.length.toLocaleString()} 个有效点`,
              detail: currentVehicleKeyword
                ? '围绕当前车辆生成可播放轨迹，用于定位复盘和客户问询。'
                : '先输入 VIN、车牌或手机号，再生成单车轨迹。',
              primaryText: '播放轨迹',
              secondaryText: '复制摘要',
              disabled: validLocations.length === 0,
              onPrimary: () => openQueryTab('location'),
              onSecondary: copyTrajectorySummary
            },
            {
              title: '位置导出',
              tag: locations.total > 0 ? '可交付' : '无数据',
              tagColor: locations.total > 0 ? 'green' as const : 'grey' as const,
              value: `${locations.total.toLocaleString()} 条位置`,
              detail: '导出当前筛选范围的位置历史，支撑客户定位、BI 核对和离线复盘。',
              primaryText: '导出位置',
              secondaryText: '车辆服务',
              disabled: locations.items.length === 0,
              secondaryDisabled: !currentVehicleKeyword,
              onPrimary: exportLocations,
              onSecondary: () => currentVehicleKeyword && onOpenVehicle(currentVehicleKeyword, currentProtocol)
            },
            {
              title: '明细证据',
              tag: (rawFrames.total ?? 0) > 0 ? '有明细' : '待加载',
              tagColor: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'orange' as const,
              value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
              detail: '保留接入侧证据，用于解释位置、里程和字段值的来源。',
              primaryText: '看证据',
              secondaryText: '导出证据',
              disabled: false,
              secondaryDisabled: rawFrames.items.length === 0,
              onPrimary: () => openQueryTab('raw'),
              onSecondary: exportRawFrames
            },
            {
              title: '字段裁剪',
              tag: selectedFieldCount > 0 ? `${selectedFieldCount} 个字段` : '全量字段',
              tagColor: rawFieldRows.length > 0 ? 'green' as const : 'blue' as const,
              value: `${rawFieldRows.length.toLocaleString()} 个字段`,
              detail: selectedFieldCount > 0
                ? '按配置字段返回明细，减少接口体积并加快复核。'
                : '需要字段级核对时，切到字段裁剪或填写字段裁剪。',
              primaryText: '字段裁剪',
              secondaryText: '导出字段',
              disabled: false,
              secondaryDisabled: rawFieldRows.length === 0,
              onPrimary: () => openQueryTab('fields'),
              onSecondary: exportRawFields
            }
          ].map((task) => (
            <div key={task.title} className="vp-history-delivery-item">
              <div className="vp-history-delivery-head">
                <Typography.Text strong>{task.title}</Typography.Text>
                <Tag color={task.tagColor}>{task.tag}</Tag>
              </div>
              <strong>{task.value}</strong>
              <Typography.Text type="secondary">{task.detail}</Typography.Text>
              <Space wrap>
                <Button size="small" theme="solid" type="primary" disabled={task.disabled} onClick={task.onPrimary}>
                  {task.primaryText}
                </Button>
                <Button size="small" disabled={task.secondaryDisabled} onClick={task.onSecondary}>
                  {task.secondaryText}
                </Button>
              </Space>
            </div>
          ))}
        </div>
      </Card>
      <Card bordered title={mode === 'query' ? '历史数据质量' : '客户轨迹影响'} style={{ marginTop: 16 }}>
        <div className="vp-trajectory-impact-board">
          <div className="vp-trajectory-impact-summary">
            <Space wrap>
              <Tag color={hasTrajectoryAnomaly ? 'orange' : 'green'}>
                {hasTrajectoryAnomaly ? '需要复核' : '可用于业务回放'}
              </Tag>
              <Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图已接入' : '地图待接入'}</Tag>
            </Space>
            <Typography.Text strong>{currentVehicleKeyword || '全部车辆'}</Typography.Text>
            <Typography.Text type="secondary">
              轨迹、历史查询导出、明细证据和里程统计按同一车辆范围联动，用于客户复盘、客户问询和断链说明。
            </Typography.Text>
            <Space wrap>
              <Button size="small" icon={<IconCopy />} onClick={copyTrajectoryImpactPackage}>复制客户影响</Button>
              <Button size="small" disabled={!currentVehicleKeyword} onClick={() => onOpenVehicle(currentVehicleKeyword, currentProtocol)}>轨迹车辆服务</Button>
              <Button size="small" disabled={!onOpenMileage} onClick={() => onOpenMileage?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, ...(filters.dateFrom ? { dateFrom: filters.dateFrom } : {}), ...(filters.dateTo ? { dateTo: filters.dateTo } : {}) })}>里程统计</Button>
            </Space>
          </div>
          <div className="vp-trajectory-impact-grid">
            {[
              {
                label: '轨迹范围',
                value: `${locations.total.toLocaleString()} 点`,
                detail: `${validLocations.length.toLocaleString()} 个有效坐标，当前页覆盖可回放轨迹。`,
                color: 'blue' as const
              },
              {
                label: '定位覆盖',
                value: formatPercent(trajectoryCoverageRate),
                detail: `回放跨度 ${formatDurationMinutes(playbackSpanMinutes)}，采样 ${formatDurationMinutes(playbackIntervalMinutes, '/点')}。`,
                color: (trajectoryCoverageRate ?? 0) >= 80 ? 'green' as const : 'orange' as const
              },
              {
                label: '异常影响',
                value: `${(trajectoryAnomalies.gapCount + trajectoryAnomalies.mileageRollbackCount + trajectoryAnomalies.overspeedCount).toLocaleString()} 项`,
                detail: `断点 ${trajectoryAnomalies.gapCount.toLocaleString()}，里程回退 ${trajectoryAnomalies.mileageRollbackCount.toLocaleString()}，超速 ${trajectoryAnomalies.overspeedCount.toLocaleString()}。`,
                color: hasTrajectoryAnomaly ? 'orange' as const : 'green' as const
              },
              {
                label: '明细证据',
                value: `${(rawFrames.total ?? 0).toLocaleString()} 帧`,
                detail: rawFieldRows.length > 0 ? `${rawFieldRows.length.toLocaleString()} 个字段裁剪可直接导出。` : '可切换明细证据并请求字段裁剪。',
                color: (rawFrames.total ?? 0) > 0 ? 'blue' as const : 'grey' as const
              },
              {
                label: '地图能力',
                value: amapConfigured ? '高德可用' : '待配置',
                detail: amapConfigured ? '可打开高德线路并解析当前回放点地址。' : '配置高德 Web 服务和 JS API 后启用地图能力。',
                color: amapConfigured ? 'green' as const : 'orange' as const
              }
            ].map((item) => (
              <div key={item.label} className="vp-trajectory-impact-item">
                <Tag color={item.color}>{item.label}</Tag>
                <strong>{item.value}</strong>
                <Typography.Text type="secondary">{item.detail}</Typography.Text>
              </div>
            ))}
          </div>
        </div>
      </Card>
      <Card
        bordered
        title={(
          <Space>
            <span>{mode === 'query' ? '轨迹预览与复核' : '轨迹回放作业台'}</span>
            <Button size="small" theme="light" icon={<IconCopy />} onClick={copyTrajectorySummary}>复制轨迹摘要</Button>
            <Button size="small" theme="light" icon={<IconCopy />} onClick={copyHistoryEvidencePackage}>复制数据复核包</Button>
            <Button size="small" theme="light" icon={<IconCopy />} onClick={copyTrajectoryReviewPackage}>复制轨迹复盘包</Button>
            <Button size="small" theme="light" disabled={validLocations.length === 0} onClick={openAmapTrajectory}>高德线路</Button>
          </Space>
        )}
        style={{ marginTop: 16 }}
      >
        <div className="vp-playback-layout">
          <div className="vp-playback-map">
            <div className="vp-monitor-map-header">
              <Space wrap>
                <Tag color="blue">{validLocations.length.toLocaleString()} 个有效轨迹点</Tag>
                <Tag color={currentProtocol ? 'blue' : 'green'}>{currentProtocol || '全部来源证据'}</Tag>
                <Tag color="green">{formatNumber(mileageDelta, ' km')}</Tag>
                <Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '高德地图配置就绪' : '高德地图待配置'}</Tag>
              </Space>
            </div>
            <VehicleMap
              points={playbackPoints}
              mode="track"
              selectedId={currentPlaybackPointId}
              onPointSelect={selectPlaybackMapPoint}
              fallbackLabel="高德地图未配置，显示轨迹坐标预览"
            />
          </div>
          <div className="vp-playback-side">
            {[
              { label: '轨迹点', value: locations.total.toLocaleString(), color: 'blue' as const },
              { label: '有效定位', value: validLocations.length.toLocaleString(), color: 'green' as const },
              { label: '区间里程', value: formatNumber(mileageDelta, ' km'), color: 'green' as const },
              { label: '最高速度', value: formatNumber(maxSpeed, ' km/h'), color: 'orange' as const }
            ].map((item) => (
              <div key={item.label} className="vp-monitor-metric">
                <Tag color={item.color}>{item.label}</Tag>
                <div className="vp-monitor-metric-value">{item.value}</div>
              </div>
            ))}
          <div className="vp-playback-current">
              <div className="vp-map-service-queue-title">当前回放点</div>
              {currentPlayback ? (
                <>
                  <Space wrap>
                    <Tag color="blue">点 {currentPlaybackIndex + 1} / {playbackRows.length}</Tag>
                    <Tag color={currentPlayback.protocol ? 'blue' : 'grey'}>{currentPlayback.protocol || '-'}</Tag>
                  </Space>
                  <Typography.Text strong>{currentPlayback.plate || currentPlayback.vin}</Typography.Text>
                  <Typography.Text type="tertiary" size="small">{currentPlayback.deviceTime || currentPlayback.serverTime || '-'}</Typography.Text>
                  <Space wrap>
                    <Tag color="green">{formatNumber(currentPlayback.speedKmh, ' km/h')}</Tag>
                    <Tag color="blue">{formatNumber(currentPlayback.totalMileageKm, ' km')}</Tag>
                  </Space>
                  <div className="vp-playback-address">
                    <Tag color={currentPlaybackAddress ? 'green' : 'grey'}>{currentPlaybackAddress?.provider || '地址未解析'}</Tag>
                    <Typography.Text type={currentPlaybackAddress ? 'secondary' : 'tertiary'} ellipsis={{ showTooltip: true }}>
                      {currentPlaybackAddress?.formattedAddress || `${currentPlayback.longitude}, ${currentPlayback.latitude}`}
                    </Typography.Text>
                  </div>
                  <Space wrap>
                    <Button
                      size="small"
                      aria-label={playbackPlaying ? '暂停轨迹' : '播放轨迹'}
                      disabled={playbackRows.length <= 1}
                      onClick={togglePlayback}
                    >
                      {playbackPlaying ? '暂停' : playbackAtEnd ? '重播' : '播放'}
                    </Button>
                    <Typography.Text type="secondary" size="small">播放速度</Typography.Text>
                    <Select
                      size="small"
                      value={String(playbackSpeedMs)}
                      style={{ width: 108 }}
                      onChange={(value) => setPlaybackSpeedMs(Number(value))}
                    >
                      <Select.Option value="1800">慢速</Select.Option>
                      <Select.Option value="1200">标准</Select.Option>
                      <Select.Option value="600">快速</Select.Option>
                    </Select>
                  </Space>
                  <Space wrap>
                    <Button size="small" disabled={currentPlaybackIndex <= 0} onClick={() => movePlayback(-1)}>上一点</Button>
                    <Button size="small" disabled={currentPlaybackIndex >= playbackRows.length - 1} onClick={() => movePlayback(1)}>下一点</Button>
                    <Button size="small" loading={reverseGeocoding} disabled={!hasValidCoordinate(currentPlayback)} onClick={resolveCurrentPlaybackAddress}>解析地址</Button>
                    <Button size="small" disabled={!canOpenVehicle(currentPlayback.vin)} onClick={openPlaybackVehicle}>回放点车辆服务</Button>
                  </Space>
                  <input
                    className="vp-playback-range"
                    aria-label="轨迹回放进度"
                    type="range"
                    min={0}
                    max={Math.max(playbackRows.length - 1, 0)}
                    step={1}
                    value={currentPlaybackIndex}
                    onChange={(event) => selectPlaybackPoint(Number(event.currentTarget.value))}
                  />
                </>
              ) : (
                <Typography.Text type="tertiary">暂无可回放位置点</Typography.Text>
              )}
            </div>
          </div>
        </div>
        <div className="vp-playback-timeline">
          {(validLocations.length > 0 ? validLocations : locationItems).slice(0, 8).map((row, index) => (
            <button
              key={`${row.deviceTime}-${index}`}
              type="button"
              className={`vp-playback-step ${index === currentPlaybackIndex ? 'vp-playback-step-active' : ''}`}
              aria-label={`选择轨迹点 ${index + 1}`}
              onClick={() => selectPlaybackPoint(index)}
            >
              <Tag color={index === 0 ? 'green' : index === Math.min((validLocations.length || locationItems.length), 8) - 1 ? 'orange' : 'blue'}>
                {index === 0 ? '起点' : index === Math.min((validLocations.length || locationItems.length), 8) - 1 ? '终点' : `点 ${index + 1}`}
              </Tag>
              <div className="vp-evidence-value">{row.deviceTime || row.serverTime || '-'}</div>
              <Typography.Text type="secondary">
                {formatNumber(row.speedKmh, ' km/h')} / {formatNumber(row.totalMileageKm, ' km')}
              </Typography.Text>
            </button>
          ))}
          {locationItems.length === 0 ? (
            <Typography.Text type="secondary">当前筛选范围暂无轨迹点，请调整车辆、来源证据或时间范围。</Typography.Text>
          ) : null}
        </div>
        <Card bordered title="轨迹复盘质量" style={{ marginTop: 12 }}>
          <div className="vp-stat-insight-grid">
            {[
              {
                label: '定位覆盖率',
                value: formatPercent(trajectoryCoverageRate),
                color: (trajectoryCoverageRate ?? 0) >= 80 ? 'green' as const : 'orange' as const,
                detail: `${validLocations.length.toLocaleString()} / ${locationItems.length.toLocaleString()} 个点有有效坐标。`
              },
              {
                label: '回放跨度',
                value: formatDurationMinutes(playbackSpanMinutes),
                color: isFiniteNumber(playbackSpanMinutes) ? 'blue' as const : 'grey' as const,
                detail: '当前页首末轨迹点设备时间跨度。'
              },
              {
                label: '采样间隔',
                value: formatDurationMinutes(playbackIntervalMinutes, '/点'),
                color: isFiniteNumber(playbackIntervalMinutes) && playbackIntervalMinutes <= 10 ? 'green' as const : 'orange' as const,
                detail: '当前页估算的平均轨迹采样间隔。'
              }
            ].map((item) => (
              <Card key={item.label} bordered>
                <Tag color={item.color}>{item.label}</Tag>
                <div className="vp-monitor-metric-value">{item.value}</div>
                <Typography.Text type="secondary">{item.detail}</Typography.Text>
              </Card>
            ))}
          </div>
        </Card>
        <Card bordered title="轨迹异常判读" style={{ marginTop: 12 }}>
          <div className="vp-trajectory-anomaly-grid">
            {[
              {
                label: '数据断点',
                value: trajectoryAnomalies.gapCount > 0 ? `${trajectoryAnomalies.gapCount.toLocaleString()} 处` : '无',
                detail: trajectoryAnomalies.gapCount > 0 ? `最大断点 ${formatDurationMinutes(trajectoryAnomalies.maxGapMinutes)}` : '相邻轨迹点未发现超过 30 分钟断点。',
                color: trajectoryAnomalies.gapCount > 0 ? 'orange' as const : 'green' as const
              },
              {
                label: '里程回退',
                value: trajectoryAnomalies.mileageRollbackCount > 0 ? formatNumber(trajectoryAnomalies.maxMileageRollbackKm, ' km') : '无',
                detail: trajectoryAnomalies.mileageRollbackCount > 0 ? `${trajectoryAnomalies.mileageRollbackCount.toLocaleString()} 处总里程低于上一点。` : '当前页总里程单调不回退。',
                color: trajectoryAnomalies.mileageRollbackCount > 0 ? 'red' as const : 'green' as const
              },
              {
                label: '速度异常',
                value: trajectoryAnomalies.overspeedCount > 0 ? formatNumber(trajectoryAnomalies.maxSpeedKmh, ' km/h') : '无',
                detail: trajectoryAnomalies.overspeedCount > 0 ? `${trajectoryAnomalies.overspeedCount.toLocaleString()} 个点超过 120 km/h。` : '当前页未发现超过 120 km/h 的速度点。',
                color: trajectoryAnomalies.overspeedCount > 0 ? 'orange' as const : 'green' as const
              }
            ].map((item) => (
              <div key={item.label} className="vp-trajectory-anomaly-item">
                <Tag color={item.color}>{item.label}</Tag>
                <div className="vp-monitor-metric-value">{item.value}</div>
                <Typography.Text type="secondary">{item.detail}</Typography.Text>
              </div>
            ))}
          </div>
          <div className="vp-trajectory-anomaly-action">
            <Tag color={hasTrajectoryAnomaly ? 'orange' : 'green'}>{hasTrajectoryAnomaly ? '建议核对明细证据和当日里程统计' : '轨迹质量可用'}</Tag>
            <Typography.Text type="secondary">
              {hasTrajectoryAnomaly ? '异常点会影响轨迹回放、定位复盘和区间里程判断，请结合字段裁剪与里程统计交叉确认。' : '当前页轨迹未发现明显断点、里程回退或速度异常。'}
            </Typography.Text>
          </div>
        </Card>
        <Space wrap style={{ marginTop: 12 }}>
          <Tag color="grey">起点：{firstLocation?.deviceTime || firstLocation?.serverTime || '-'}</Tag>
          <Tag color="grey">终点：{lastLocation?.deviceTime || lastLocation?.serverTime || '-'}</Tag>
          <Tag color="grey">地图接入：高德 JS API 运行时配置</Tag>
        </Space>
      </Card>
      <Card bordered style={{ marginTop: 16 }}>
        <Tabs activeKey={activeTab} onChange={(key) => changeTab(String(key))}>
          <Tabs.TabPane tab="位置历史" itemKey="location">
            <div className="vp-table-toolbar">
              <Space wrap>
                <Tag color="blue">当前页 {locations.items.length.toLocaleString()} 条</Tag>
                <Button size="small" onClick={exportLocations}>导出位置当前页 CSV</Button>
              </Space>
            </div>
            <Table
              rowKey="deviceTime"
              dataSource={locations.items}
              loading={loadingLocations}
              pagination={{
                currentPage: locationPagination.currentPage,
                pageSize: locationPagination.pageSize,
                total: locations.total,
                showSizeChanger: true,
                onPageChange: (page) => loadLocations(filters, page, locationPagination.pageSize),
                onPageSizeChange: (pageSize) => loadLocations(filters, 1, pageSize)
              }}
              columns={[
                { title: 'VIN', dataIndex: 'vin', width: 190 },
                { title: '来源证据', dataIndex: 'protocol', width: 120 },
                { title: '经度', dataIndex: 'longitude', width: 120 },
                { title: '纬度', dataIndex: 'latitude', width: 120 },
                { title: '速度 km/h', dataIndex: 'speedKmh', width: 120 },
                { title: '总里程 km', dataIndex: 'totalMileageKm', width: 130 },
                { title: '设备时间', dataIndex: 'deviceTime', width: 190 },
                { title: '入库时间', dataIndex: 'serverTime', width: 190 },
                {
                  title: '操作',
                  width: 280,
                  render: (_: unknown, row: HistoryLocationRow) => (
                    <Space wrap>
                      <Button disabled={!canOpenVehicle(row.vin)} onClick={() => onOpenVehicle(row.vin, row.protocol)}>车辆服务</Button>
                      <Button disabled={!canOpenVehicle(row.vin) || !onOpenMileage} onClick={() => openLocationMileage(row)}>里程统计</Button>
                      <Button disabled={!canOpenVehicle(row.vin) || !onOpenRaw} onClick={() => openLocationRaw(row)}>核对历史明细</Button>
                    </Space>
                  )
                }
              ]}
            />
          </Tabs.TabPane>
          <Tabs.TabPane tab="历史明细" itemKey="raw">
            <div className="vp-table-toolbar">
              <Space wrap>
                <Tag color="blue">当前页 {rawFrames.items.length.toLocaleString()} 条</Tag>
                <Tag color={isIncludeFieldsEnabled(filters.includeFields) || splitFields(filters.fields).length > 0 ? 'green' : 'orange'}>
                  {isIncludeFieldsEnabled(filters.includeFields) || splitFields(filters.fields).length > 0 ? '包含字段明细' : '未请求字段明细'}
                </Tag>
                <Button size="small" onClick={exportRawFrames}>导出历史明细当前页 CSV</Button>
              </Space>
            </div>
            <Table
              rowKey="id"
              dataSource={rawFrames.items}
              loading={loadingRaw}
              pagination={{
                currentPage: rawPagination.currentPage,
                pageSize: rawPagination.pageSize,
                total: rawFrames.total,
                showSizeChanger: true,
                onPageChange: (page) => loadRawFrames(filters, page, rawPagination.pageSize),
                onPageSizeChange: (pageSize) => loadRawFrames(filters, 1, pageSize)
              }}
              columns={[
                { title: 'ID', dataIndex: 'id', width: 260 },
                { title: 'VIN', dataIndex: 'vin', width: 190 },
                { title: '车牌', dataIndex: 'plate', width: 120 },
                { title: '来源证据', dataIndex: 'protocol', width: 120 },
                { title: '帧类型', dataIndex: 'frameType', width: 190 },
                { title: '大小 B', dataIndex: 'rawSizeBytes', width: 100 },
                { title: '设备时间', dataIndex: 'deviceTime', width: 190 },
                { title: '入库时间', dataIndex: 'serverTime', width: 190 },
                {
                  title: '操作',
                  width: 280,
                  render: (_: unknown, row: RawFrameRow) => (
                    <Space wrap>
                      <Button onClick={() => setSelectedRaw(row)}>字段</Button>
                      <Button disabled={!canOpenVehicle(row.vin) || !onOpenMileage} onClick={() => openRawMileage(row)}>里程统计</Button>
                      <Button disabled={!canOpenVehicle(row.vin)} onClick={() => onOpenVehicle(row.vin, row.protocol)}>车辆服务</Button>
                    </Space>
                  )
                }
              ]}
            />
          </Tabs.TabPane>
          <Tabs.TabPane tab="字段明细" itemKey="fields">
            <div className="vp-table-toolbar">
              <Space wrap>
                <Tag color="green">当前页 {rawFieldRows.length.toLocaleString()} 个字段</Tag>
                <Tag color="blue">{rawFrames.items.length.toLocaleString()} 条历史明细</Tag>
                {selectedFieldCount > 0 ? <Tag color="blue">字段裁剪 {selectedFieldCount.toLocaleString()} 个</Tag> : <Tag color="grey">全量字段明细</Tag>}
                <Button size="small" onClick={exportRawFields}>导出字段明细当前页 CSV</Button>
              </Space>
            </div>
            <Table
              rowKey="id"
              dataSource={rawFieldRows}
              loading={loadingRaw}
              pagination={false}
              columns={[
                { title: '字段', dataIndex: 'fieldPath', width: 320 },
                {
                  title: '值',
                  width: 260,
                  render: (_: unknown, row: RawFieldRow) => (
                    <Typography.Text ellipsis={{ showTooltip: true }}>{formatFieldValue(row.fieldValue) || '-'}</Typography.Text>
                  )
                },
                { title: 'VIN', dataIndex: 'vin', width: 190 },
                { title: '车牌', dataIndex: 'plate', width: 120 },
                { title: '来源证据', dataIndex: 'protocol', width: 120 },
                { title: '帧类型', dataIndex: 'frameType', width: 190 },
                { title: '设备时间', dataIndex: 'deviceTime', width: 190 },
                { title: '历史明细ID', dataIndex: 'rawId', width: 260 },
                {
                  title: '操作',
                  width: 170,
                  render: (_: unknown, row: RawFieldRow) => (
                    <Space wrap>
                      <Button disabled={!canOpenVehicle(row.vin)} onClick={() => onOpenVehicle(row.vin, row.protocol)}>车辆服务</Button>
                    </Space>
                  )
                }
              ]}
            />
            <Space wrap style={{ marginTop: 12 }}>
              <Tag color="grey">分页沿用历史明细分页</Tag>
              <Tag color="grey">字段来自 parsedFields，保留协议字段映射后的路径</Tag>
            </Space>
          </Tabs.TabPane>
        </Tabs>
      </Card>
      <SideSheet title="历史明细字段明细" visible={Boolean(selectedRaw)} onCancel={() => setSelectedRaw(null)} width={720}>
        <Space vertical align="start" spacing={12} style={{ width: '100%' }}>
          <Typography.Text type="tertiary">
            {selectedRaw?.protocol ?? '-'} / {selectedRaw?.plate || selectedRaw?.vin || '-'} / {rawFieldCount} 个字段
          </Typography.Text>
          {rawFieldCount === 0 ? (
            <Typography.Text type="secondary">当前查询未返回字段明细，请勾选“返回字段明细”或配置字段裁剪后重新查询。</Typography.Text>
          ) : null}
        </Space>
        <pre className="vp-json">{JSON.stringify(selectedRaw?.parsedFields ?? {}, null, 2)}</pre>
      </SideSheet>
    </div>
  );
}
