import {
  alertEvents,
  dataSources,
  getVehiclePrimarySource,
  getVehicleProtocols,
  statusLabel,
  vehicles
} from './mockData';
import { expandRawFrameFields } from './rawFieldMap';
import type { AlertEvent, Protocol, TrackPoint, VehicleRecord, VehicleSourceSnapshot, VehicleStatus } from './types';

export type SectionKey = 'command' | 'replay' | 'history' | 'alerts' | 'stats' | 'sources';
export type ProtocolFilter = Protocol | 'ALL';
export type StatusFilter = VehicleStatus | 'ALL';

export type SourceConsistencyProjection = {
  sourceCount: number;
  onlineSourceCount: number;
  locatedSourceCount: number;
  missingProtocols: Protocol[];
  mileageDeltaKm: number;
  sourceTimeDeltaSeconds: number;
  severity: 'ok' | 'warning' | 'error';
  title: string;
  detail: string;
};

export type CapabilityBoundary = {
  capability: string;
  source: string;
  entry: string;
  boundary: string;
};

export type DataCapabilityState = 'ready' | 'partial' | 'waiting' | 'unavailable';

export type RuntimeCapabilityStatus = 'disabled' | 'loading' | 'ready' | 'stale' | 'fallback' | 'error';

export type MapRuntimeCapabilityStatus = 'disabled' | 'loading' | 'ready' | 'error';

export type DataCapabilityRadarItem = {
  key: 'realtime-location' | 'protocol-coverage' | 'amap-map' | 'raw-evidence' | 'mileage-stat';
  title: string;
  state: DataCapabilityState;
  metric: string;
  evidence: string;
  boundary: string;
};

export type DataCapabilityRadarInput = {
  vehicles: VehicleRecord[];
  realtimeStatus: RuntimeCapabilityStatus;
  realtimeTotal?: number;
  amapStatus: MapRuntimeCapabilityStatus;
  rawQueryEnabled: boolean;
  trackQueryEnabled: boolean;
};

export type VehicleDetailDigestTone = 'ok' | 'warning' | 'error' | 'ready';

export type VehicleDetailDigestItem = {
  key: 'realtime' | 'trust' | 'traceability';
  title: string;
  value: string;
  metric: string;
  source: string;
  detail: string;
  tone: VehicleDetailDigestTone;
};

export type HistoryQueryPreview = {
  endpoint: string;
  method: 'POST';
  storage: string;
  payload: {
    vin?: string;
    protocol?: Protocol;
    dateFrom: string;
    dateTo: string;
    fields: string[];
    includeFields: boolean;
    limit: number;
    offset: number;
  };
};

export type SampleBoundarySurface = 'history-raw' | 'alert-events' | 'source-monitor';

export type SampleBoundaryCopy = {
  title: string;
  detail: string;
  actionLabel: string;
};

export type DailyMileageProjection = {
  id: string;
  plate: string;
  vin: string;
  protocol: Protocol;
  sourceName: string;
  todayMileageKm: number;
  totalMileageKm: number;
  lastSeen: string;
  online: boolean;
};

export type ProtocolMileageProjection = {
  protocol: Protocol;
  label: string;
  vehicleCount: number;
  onlineCount: number;
  dailyMileageKm: number;
  latestTotalMileageKm: number;
  rowCount: number;
};

export type MileageSourceDecisionStatus = 'ready' | 'warning' | 'conflict' | 'missing';

export type MileageSourceDecisionSource = {
  id: string;
  protocol: Protocol;
  sourceName: string;
  todayMileageKm: number;
  totalMileageKm: number;
  online: boolean;
  lastSeen: string;
};

export type MileageSourceDecision = {
  id: string;
  plate: string;
  vin: string;
  selectedProtocol?: Protocol;
  selectedSourceName: string;
  selectedDailyMileageKm: number;
  selectedTotalMileageKm: number;
  candidateCount: number;
  sourceCount: number;
  status: MileageSourceDecisionStatus;
  title: string;
  detail: string;
  sources: MileageSourceDecisionSource[];
};

export type AlertSummaryProjection = {
  label: string;
  value: string;
  sub: string;
  tone: 'blue' | 'green' | 'orange' | 'cyan';
};

export type AlertEvidenceProjection = {
  title: string;
  source: string;
  detail: string;
  status: 'ready' | 'warning';
};

export type RealtimeSummaryProjection = {
  label: string;
  value: string;
  sub: string;
  tone: 'blue' | 'green' | 'orange' | 'cyan';
};

export type RealtimeEvidenceProjection = {
  label: string;
  source: string;
  value: string;
};

export type CommandPriorityTone = 'ok' | 'warning' | 'error';

export type CommandPriorityItem = {
  key: 'source-health' | 'coordinate' | 'mileage' | 'field-coverage';
  title: string;
  value: string;
  detail: string;
  tone: CommandPriorityTone;
  vehicleIds: string[];
};

export type VehicleDiagnosticStep = {
  key: 'realtime-state' | 'source-consistency' | 'evidence-loop';
  title: string;
  status: 'ready' | 'warning' | 'error';
  primary: string;
  detail: string;
  action: 'overview' | 'sources' | 'history';
};

export type VehicleSourceResponsibilityRow = {
  id: string;
  protocol: Protocol;
  sourceName: string;
  endpoint: string;
  selected: boolean;
  status: 'ready' | 'warning' | 'error';
  statusLabel: string;
  lastSeen: string;
  latencyMs: number;
  fieldCount: number;
  totalMileageKm?: number;
  todayMileageKm?: number;
  mileageDeltaKm: number;
  issueCount: number;
  issueSummary: string;
};

export type StatsInterpretationTone = 'ready' | 'warning' | 'error' | 'waiting';

export type StatsInterpretationItem = {
  key: 'trusted-mileage' | 'daily-mileage-api' | 'source-consistency' | 'protocol-coverage';
  title: string;
  metric: string;
  detail: string;
  evidence: string;
  tone: StatsInterpretationTone;
};

export type ReplaySummaryProjection = {
  label: string;
  value: string;
  sub: string;
  tone: 'blue' | 'green' | 'orange' | 'cyan';
};

export type ReplaySegmentProjection = {
  id: string;
  from: string;
  to: string;
  distanceKm: number;
  speedDeltaKph: number;
  state: TrackPoint['state'];
};

export type ReplayEvidenceProjection = {
  title: string;
  source: string;
  detail: string;
  status: 'ready' | 'warning';
};

export const sectionTitle: Record<SectionKey, { title: string; subtitle: string }> = {
  command: { title: '实时指挥台', subtitle: '按车辆服务视角查看位置、来源、实时字段和异常' },
  replay: { title: '轨迹回放', subtitle: '用时间窗串联位置、里程、停留和告警证据' },
  history: { title: '历史查询', subtitle: '查询 raw frame 与扁平字段，支持按需裁剪返回字段' },
  alerts: { title: '告警中心', subtitle: '用实时与历史证据观测断链、字段缺失和协议告警位' },
  stats: { title: '统计分析', subtitle: '查看实时覆盖、在线状态、速度和最新总里程' },
  sources: { title: '数据源监控', subtitle: '维护平台来源、发送端、协议健康和最近上报情况' }
};

export const sectionDataBoundaries: Record<SectionKey, Array<{ label: string; value: string }>> = {
  command: [
    { label: 'Redis', value: 'vehicle:online / vehicle:latest / realtime-raw' },
    { label: 'MySQL', value: 'vehicle_realtime_snapshot / vehicle_realtime_location' }
  ],
  replay: [
    { label: 'TDengine', value: 'vehicle_locations 按 VIN + 时间窗查询' },
    { label: '证据', value: 'raw_frames 回溯 / vehicle_daily_mileage 校验' }
  ],
  history: [
    { label: 'TDengine', value: 'raw_frames / raw_frame_payload_chunks' },
    { label: 'BFF', value: 'raw-frames query 支持字段裁剪' }
  ],
  alerts: [
    { label: '观测', value: 'realtime locations / vehicle_locations' },
    { label: '来源', value: 'Redis 在线态 + TDengine 历史 + MySQL 投影' }
  ],
  stats: [
    { label: 'MySQL', value: 'vehicle_daily_mileage 按 VIN + 日期 + 协议' },
    { label: 'Redis', value: 'vehicle:online / vehicle:last_seen' }
  ],
  sources: [
    { label: 'MySQL', value: 'vehicle_data_source / jt808_registration' },
    { label: 'Redis', value: 'online key 统计当前活跃车辆' }
  ]
};

export const statusFilterOptions: Array<{ value: StatusFilter; label: string }> = [
  { value: 'ALL', label: '全部状态' },
  { value: 'online', label: '在线' },
  { value: 'warning', label: '关注' },
  { value: 'offline', label: '离线' }
];

export const protocolFilterOptions: Array<{ value: ProtocolFilter; label: string }> = [
  { value: 'ALL', label: '全部协议' },
  { value: 'GB32960', label: 'GB32960' },
  { value: 'JT808', label: 'JT808' },
  { value: 'YUTONG_MQTT', label: '宇通 MQTT' }
];

export const historyPageSizeOptions = [2, 3, 5];

export const capabilityBoundaries: CapabilityBoundary[] = [
  {
    capability: '车辆是否在线',
    source: 'Redis vehicle:online / vehicle:last_seen',
    entry: '实时指挥台、统计分析',
    boundary: '只表达最近活跃，不推断长期运营状态'
  },
  {
    capability: '实时位置与当前态',
    source: 'MySQL vehicle_realtime_location / vehicle_realtime_snapshot',
    entry: '地图、车辆详情',
    boundary: '只展示实时数据帧，不展示登录和鉴权帧'
  },
  {
    capability: '轨迹回放',
    source: 'TDengine vehicle_locations',
    entry: '轨迹回放',
    boundary: '按 VIN + 时间窗回放，不做源头无法证明的路径补全'
  },
  {
    capability: '历史 RAW 与字段',
    source: 'TDengine raw_frames / payload_chunks',
    entry: '历史查询',
    boundary: '支持字段裁剪查询，不在前端做二次协议解析'
  },
  {
    capability: '日里程统计',
    source: 'MySQL vehicle_daily_mileage',
    entry: '统计分析',
    boundary: '按 VIN + 日期 + 协议展示，不把多协议强行合并'
  },
  {
    capability: '告警观测信号',
    source: 'realtime locations / vehicle_locations',
    entry: '告警中心',
    boundary: '当前只展示观测信号，不提供通知、派单或人工处理状态流转'
  }
];

export function buildDataCapabilityRadar({
  vehicles: scopedVehicles,
  realtimeStatus,
  realtimeTotal,
  amapStatus,
  rawQueryEnabled,
  trackQueryEnabled
}: DataCapabilityRadarInput): DataCapabilityRadarItem[] {
  const sources = scopedVehicles.flatMap((vehicle) => vehicle.sources);
  const locatedVehicles = scopedVehicles.filter((vehicle) => vehicle.longitude && vehicle.latitude).length;
  const protocolVehicleCounts = scopedVehicles.reduce<Record<Protocol, number>>((counts, vehicle) => {
    const vehicleProtocols = new Set(vehicle.sources.map((source) => source.protocol));
    vehicleProtocols.forEach((protocol) => {
      counts[protocol] += 1;
    });
    return counts;
  }, { GB32960: 0, JT808: 0, YUTONG_MQTT: 0 });
  const coveredProtocolCount = Object.values(protocolVehicleCounts).filter((count) => count > 0).length;
  const mileageSources = sources.filter((source) =>
    source.totalMileageKm != null ||
    source.todayMileageKm != null
  ).length;
  const realtimeHasRows = scopedVehicles.length > 0;
  const realtimeState: DataCapabilityState = realtimeHasRows
    ? realtimeStatus === 'ready'
      ? 'ready'
      : realtimeStatus === 'loading' || realtimeStatus === 'stale'
        ? 'partial'
        : 'partial'
    : realtimeStatus === 'loading'
      ? 'waiting'
      : 'unavailable';
  const protocolState: DataCapabilityState = coveredProtocolCount === 3
    ? 'ready'
    : coveredProtocolCount > 0
      ? 'partial'
      : realtimeStatus === 'loading'
        ? 'waiting'
        : 'unavailable';
  const mapState: DataCapabilityState = amapStatus === 'ready' && locatedVehicles > 0
    ? 'ready'
    : amapStatus === 'loading'
      ? 'waiting'
      : locatedVehicles > 0
        ? 'partial'
        : 'unavailable';
  const rawState: DataCapabilityState = rawQueryEnabled
    ? realtimeStatus === 'disabled'
      ? 'unavailable'
      : realtimeStatus === 'loading'
        ? 'waiting'
        : 'ready'
    : 'unavailable';
  const mileageState: DataCapabilityState = mileageSources === sources.length && sources.length > 0
    ? 'ready'
    : mileageSources > 0
      ? 'partial'
      : realtimeStatus === 'loading'
        ? 'waiting'
        : 'unavailable';

  return [
    {
      key: 'realtime-location',
      title: '实时位置投影',
      state: realtimeState,
      metric: `${scopedVehicles.length}/${realtimeTotal ?? scopedVehicles.length} 台`,
      evidence: 'GET /api/realtime/locations + /api/realtime/snapshots -> MySQL realtime projection',
      boundary: '只接受实时数据帧，登录、鉴权和注册帧不进入车辆实时态。'
    },
    {
      key: 'protocol-coverage',
      title: '协议覆盖',
      state: protocolState,
      metric: `32960 ${protocolVehicleCounts.GB32960} / 808 ${protocolVehicleCounts.JT808} / MQTT ${protocolVehicleCounts.YUTONG_MQTT}`,
      evidence: '按当前车辆来源去重统计',
      boundary: '只展示源头实际返回的协议来源，不用空协议补齐车辆服务视图。'
    },
    {
      key: 'amap-map',
      title: '高德地图落点',
      state: mapState,
      metric: `${locatedVehicles}/${scopedVehicles.length} 坐标`,
      evidence: 'AMap Web JS + realtime coordinates',
      boundary: '无经纬度不落点，不用样例坐标补生产点位。'
    },
    {
      key: 'raw-evidence',
      title: 'RAW 证据回查',
      state: rawState,
      metric: rawQueryEnabled ? (trackQueryEnabled ? 'RAW + 轨迹' : 'RAW 可查') : '未启用',
      evidence: '/api/history/raw-frames/query',
      boundary: '按需字段裁剪响应体，前端不做二次协议解析。'
    },
    {
      key: 'mileage-stat',
      title: '里程统计口径',
      state: mileageState,
      metric: `${mileageSources}/${sources.length} 来源`,
      evidence: 'vehicle_daily_mileage + realtime total_mileage',
      boundary: '按协议来源独立统计，查询层再选择可信来源，不在实时页强行合并。'
    }
  ];
}

export function getVehicleDetailDigest(
  vehicle: VehicleRecord,
  activeSource: VehicleSourceSnapshot
): VehicleDetailDigestItem[] {
  const consistency = getSourceConsistency(vehicle);
  const realtimeTone: VehicleDetailDigestTone = vehicle.status === 'online'
    ? 'ok'
    : vehicle.status === 'warning'
      ? 'warning'
      : 'error';
  const trustTone: VehicleDetailDigestTone = consistency.severity === 'ok'
    ? 'ok'
    : consistency.severity === 'error'
      ? 'error'
      : 'warning';
  const traceable = Boolean(vehicle.vin && activeSource.protocol);

  return [
    {
      key: 'realtime',
      title: '实时状态',
      value: `${formatNumber(activeSource.speedKph ?? 0, 0)} km/h`,
      metric: `${activeSource.lastSeen} / ${activeSource.latencyMs} ms`,
      source: 'Redis online + MySQL realtime snapshot/location',
      detail: `${activeSource.sourceName} 当前展示来源，字段 ${activeSource.fieldCount} 个。`,
      tone: realtimeTone
    },
    {
      key: 'trust',
      title: '来源可信度',
      value: consistency.title,
      metric: `${consistency.onlineSourceCount}/${consistency.sourceCount} 在线`,
      source: 'vehicle source consistency',
      detail: consistency.detail,
      tone: trustTone
    },
    {
      key: 'traceability',
      title: '可追溯证据',
      value: traceable ? `${activeSource.protocol} RAW` : '证据不足',
      metric: vehicle.vin || '--',
      source: 'TDengine raw_frames + vehicle_locations',
      detail: traceable
        ? '可从当前车辆跳转轨迹和 RAW，按 VIN、协议、时间窗回查原始帧与位置证据。'
        : '缺少 VIN 或协议时不能建立可靠 RAW/轨迹证据链。',
      tone: traceable ? 'ready' : 'warning'
    }
  ];
}

function diagnosticStatusFromSeverity(severity: SourceConsistencyProjection['severity']) {
  if (severity === 'ok') return 'ready' as const;
  if (severity === 'error') return 'error' as const;
  return 'warning' as const;
}

function realtimeDiagnosticStepStatus(vehicle: VehicleRecord, activeSource: VehicleSourceSnapshot) {
  if (vehicle.status === 'offline' || activeSource.status === 'offline') {
    return 'error' as const;
  }
  if (vehicle.status !== 'online' || activeSource.status !== 'online' || activeSource.fieldCount <= 0) {
    return 'warning' as const;
  }
  return 'ready' as const;
}

export function getVehicleDiagnosticSteps(
  vehicle: VehicleRecord,
  activeSource: VehicleSourceSnapshot
): VehicleDiagnosticStep[] {
  const consistency = getSourceConsistency(vehicle);
  const realtimeStatus = realtimeDiagnosticStepStatus(vehicle, activeSource);
  const traceable = Boolean(vehicle.vin && activeSource.protocol);
  const realtimePrimary: Record<VehicleDiagnosticStep['status'], string> = {
    ready: '当前态可用',
    warning: '当前态需关注',
    error: '当前态异常'
  };

  return [
    {
      key: 'realtime-state',
      title: '确认当前态',
      status: realtimeStatus,
      primary: realtimePrimary[realtimeStatus],
      detail: `${activeSource.sourceName} / ${activeSource.lastSeen} / 字段 ${activeSource.fieldCount} / 延迟 ${activeSource.latencyMs} ms`,
      action: 'overview'
    },
    {
      key: 'source-consistency',
      title: '核对来源一致性',
      status: diagnosticStatusFromSeverity(consistency.severity),
      primary: consistency.title,
      detail: `${consistency.onlineSourceCount}/${consistency.sourceCount} 来源在线，里程差 ${formatNumber(consistency.mileageDeltaKm, 1)} km，时间差 ${formatNumber(consistency.sourceTimeDeltaSeconds, 0)} s`,
      action: 'sources'
    },
    {
      key: 'evidence-loop',
      title: '回查证据闭环',
      status: traceable ? 'ready' : 'warning',
      primary: traceable ? 'RAW/轨迹可查' : '证据不足',
      detail: traceable
        ? `TDengine raw_frames + vehicle_locations，可按 ${activeSource.protocol} / VIN 回查。`
        : '缺少 VIN 或协议，暂不能建立 RAW/轨迹证据链。',
      action: 'history'
    }
  ];
}

function sourceResponsibilityIssues(
  source: VehicleSourceSnapshot,
  activeSource: VehicleSourceSnapshot
) {
  const issues: string[] = [];
  if (source.status === 'offline') {
    issues.push('来源离线');
  } else if (source.status !== 'online') {
    issues.push('来源关注');
  }
  if (source.fieldCount <= 0) {
    issues.push('字段缺失');
  }
  if (!hasMileageValue(source)) {
    issues.push('里程缺失');
  }
  const mileageDeltaKm = source.totalMileageKm != null && activeSource.totalMileageKm != null
    ? Math.abs(source.totalMileageKm - activeSource.totalMileageKm)
    : 0;
  if (mileageDeltaKm > 5) {
    issues.push('里程差异');
  }
  return { issues, mileageDeltaKm };
}

function sourceResponsibilityStatus(source: VehicleSourceSnapshot, issues: string[]) {
  if (source.status === 'offline') {
    return 'error' as const;
  }
  return issues.length > 0 ? 'warning' as const : 'ready' as const;
}

export function getVehicleSourceResponsibilityRows(
  vehicle: VehicleRecord,
  activeSource: VehicleSourceSnapshot
): VehicleSourceResponsibilityRow[] {
  return vehicle.sources
    .map((source, index) => {
      const { issues, mileageDeltaKm } = sourceResponsibilityIssues(source, activeSource);
      const status = sourceResponsibilityStatus(source, issues);
      const statusLabel: Record<VehicleSourceResponsibilityRow['status'], string> = {
        ready: '正常',
        warning: '关注',
        error: '异常'
      };
      return {
        id: source.id,
        protocol: source.protocol,
        sourceName: source.sourceName,
        endpoint: source.endpoint,
        selected: source.id === activeSource.id,
        status,
        statusLabel: statusLabel[status],
        lastSeen: source.lastSeen,
        latencyMs: source.latencyMs,
        fieldCount: source.fieldCount,
        totalMileageKm: source.totalMileageKm,
        todayMileageKm: source.todayMileageKm,
        mileageDeltaKm,
        issueCount: issues.length,
        issueSummary: issues.length > 0 ? issues.join(' / ') : '可作为候选来源',
        sortIndex: index
      };
    })
    .sort((left, right) => Number(right.selected) - Number(left.selected) || left.sortIndex - right.sortIndex)
    .map(({ sortIndex: _sortIndex, ...row }) => row);
}

function scopedVehicleByProtocol(vehicle: VehicleRecord, protocolFilter: ProtocolFilter): VehicleRecord | undefined {
  const sources = vehicle.sources.filter((source) => protocolFilter === 'ALL' || source.protocol === protocolFilter);
  if (sources.length === 0) {
    return undefined;
  }
  return {
    ...vehicle,
    primarySourceId: sources[0].id,
    sources
  };
}

export function getStatsInterpretationItems(
  vehicles: VehicleRecord[],
  protocolFilter: ProtocolFilter = 'ALL',
  dailyMileageApiReady = false
): StatsInterpretationItem[] {
  const scopedVehicles = vehicles
    .map((vehicle) => scopedVehicleByProtocol(vehicle, protocolFilter))
    .filter((vehicle): vehicle is VehicleRecord => Boolean(vehicle));
  const mileageDecisions = getMileageSourceDecisions(scopedVehicles, protocolFilter);
  const readyMileageCount = mileageDecisions.filter((decision) => decision.status === 'ready').length;
  const conflictMileageCount = mileageDecisions.filter((decision) => decision.status === 'conflict').length;
  const missingMileageCount = mileageDecisions.filter((decision) => decision.status === 'missing').length;
  const multiSourceVehicles = scopedVehicles.filter((vehicle) => vehicle.sources.length > 1);
  const sourceAttentionCount = multiSourceVehicles.filter((vehicle) => getSourceConsistency(vehicle).severity !== 'ok').length;
  const protocolCounts = scopedVehicles.reduce<Record<Protocol, number>>((counts, vehicle) => {
    const protocols = new Set(vehicle.sources.map((source) => source.protocol));
    protocols.forEach((protocol) => {
      counts[protocol] += 1;
    });
    return counts;
  }, { GB32960: 0, JT808: 0, YUTONG_MQTT: 0 });
  const coveredProtocolCount = Object.values(protocolCounts).filter((count) => count > 0).length;

  return [
    {
      key: 'trusted-mileage',
      title: '可信里程来源',
      metric: `${readyMileageCount}/${mileageDecisions.length}`,
      detail: conflictMileageCount > 0
        ? `${conflictMileageCount} 台多源里程冲突，查询层需保留候选来源。`
        : missingMileageCount > 0
          ? `${missingMileageCount} 台缺少可用里程来源。`
          : '每台车已有可展示的候选里程来源。',
      evidence: 'vehicle_daily_mileage + realtime total_mileage',
      tone: conflictMileageCount > 0
        ? 'error'
        : readyMileageCount === mileageDecisions.length && mileageDecisions.length > 0
          ? 'ready'
          : 'warning'
    },
    {
      key: 'daily-mileage-api',
      title: '日里程统计 API',
      metric: dailyMileageApiReady ? '可用' : '待接入',
      detail: dailyMileageApiReady
        ? '可展示日里程、趋势和排名。'
        : '未接入前只展示实时总里程与可信来源判读，不伪造日统计。',
      evidence: '/api/mileage/daily / vehicle_daily_mileage',
      tone: dailyMileageApiReady ? 'ready' : 'waiting'
    },
    {
      key: 'source-consistency',
      title: '多源一致性',
      metric: `${sourceAttentionCount}/${multiSourceVehicles.length} 需关注`,
      detail: multiSourceVehicles.length === 0
        ? '当前筛选下缺少同 VIN 多来源样本。'
        : sourceAttentionCount > 0
          ? '存在离线、里程差异或时间差异，需要结合来源判责。'
          : '多源车辆的在线、里程和时间差异在当前阈值内。',
      evidence: 'Redis online + MySQL realtime snapshot/location',
      tone: multiSourceVehicles.length === 0 ? 'waiting' : sourceAttentionCount > 0 ? 'warning' : 'ready'
    },
    {
      key: 'protocol-coverage',
      title: '协议覆盖结构',
      metric: `32960 ${protocolCounts.GB32960} / 808 ${protocolCounts.JT808} / MQTT ${protocolCounts.YUTONG_MQTT}`,
      detail: coveredProtocolCount === 3
        ? '三类协议均有实时样本，可做协议间覆盖观察。'
        : coveredProtocolCount > 0
          ? '当前筛选只覆盖部分协议，统计结论需注明来源范围。'
          : '当前筛选无协议样本。',
      evidence: 'realtime source rows by protocol',
      tone: coveredProtocolCount === 3 ? 'ready' : coveredProtocolCount > 0 ? 'warning' : 'waiting'
    }
  ];
}

export function formatNumber(value: number, digits = 0) {
  return value.toLocaleString('zh-CN', {
    minimumFractionDigits: digits,
    maximumFractionDigits: digits
  });
}

function formatChinaDateTime(value: number) {
  const formatter = new Intl.DateTimeFormat('en-CA', {
    timeZone: 'Asia/Shanghai',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    hour12: false,
    hourCycle: 'h23'
  });
  const parts = formatter.formatToParts(new Date(value));
  const part = (type: Intl.DateTimeFormatPartTypes) => parts.find((item) => item.type === type)?.value ?? '00';
  return `${part('year')}-${part('month')}-${part('day')}T${part('hour')}:${part('minute')}:${part('second')}+08:00`;
}

export function buildReplayRawLookupWindow(timestampMs: number, radiusMinutes = 2) {
  const safeRadiusMs = Math.max(0, radiusMinutes) * 60_000;
  return {
    dateFrom: formatChinaDateTime(timestampMs - safeRadiusMs),
    dateTo: formatChinaDateTime(timestampMs + safeRadiusMs)
  };
}

export function getSampleBoundaryCopy(surface: SampleBoundarySurface, expanded: boolean): SampleBoundaryCopy {
  const copy: Record<SampleBoundarySurface, { noun: string; expand: string; collapse: string; expandedDetail: string; closedDetail: string }> = {
    'history-raw': {
      noun: 'Raw 样例记录',
      expand: '展开样例记录',
      collapse: '收起样例记录',
      expandedDetail: '这些记录只验证分页、筛选和请求载荷布局，不代表 TDengine 查询结果。',
      closedDetail: '真实 RAW 未返回前，不展示样例记录表，避免误读为 raw_frames 查询结果。'
    },
    'alert-events': {
      noun: '样例事件',
      expand: '展开样例事件',
      collapse: '收起样例事件',
      expandedDetail: '这些记录不代表 durable alert，只用于验证交互与证据入口。',
      closedDetail: '真实告警接口未接入前，不展示样例事件数量和列表，避免误读为生产告警。'
    },
    'source-monitor': {
      noun: '样例来源',
      expand: '展开样例来源',
      collapse: '收起样例来源',
      expandedDetail: '这些记录不代表生产来源判断，仅用于验证筛选、健康状态和详情布局。',
      closedDetail: '真实证据未返回前，来源计数、健康数量和平台账号不代表生产事实。'
    }
  };
  const selected = copy[surface];
  return {
    title: expanded ? `正在展示${selected.noun}` : `${selected.noun}已收起`,
    detail: expanded ? selected.expandedDetail : selected.closedDetail,
    actionLabel: expanded ? selected.collapse : selected.expand
  };
}

export function includesQuery(vehicle: VehicleRecord, query: string) {
  const normalized = query.trim().toLowerCase();
  if (!normalized) {
    return true;
  }
  return [vehicle.plate, vehicle.vin, vehicle.phone, vehicle.oem, vehicle.city, vehicle.address]
    .filter(Boolean)
    .some((value) => String(value).toLowerCase().includes(normalized));
}

export function filterVehicles(
  query: string,
  protocolFilter: ProtocolFilter,
  statusFilter: StatusFilter,
  sourceFilter: string,
  records: VehicleRecord[] = vehicles
) {
  return records.filter((vehicle) => {
    const protocolMatched =
      protocolFilter === 'ALL' || getVehicleProtocols(vehicle).includes(protocolFilter);
    const statusMatched = statusFilter === 'ALL' || vehicle.status === statusFilter;
    const sourceMatched = sourceFilter === 'ALL' || vehicle.sources.some((source) => source.id === sourceFilter);
    return includesQuery(vehicle, query) && protocolMatched && statusMatched && sourceMatched;
  });
}

export function getKpis(filteredVehicles: VehicleRecord[], protocolFilter: ProtocolFilter = 'ALL') {
  const online = filteredVehicles.filter((vehicle) => vehicle.status === 'online').length;
  const warning = filteredVehicles.filter((vehicle) => vehicle.status === 'warning').length;
  const mileage = filteredVehicles.reduce((sum, vehicle) => {
    if (protocolFilter === 'ALL') {
      return sum + (getVehiclePrimarySource(vehicle).todayMileageKm ?? 0);
    }
    const source = vehicle.sources.find((item) => item.protocol === protocolFilter);
    return sum + (source?.todayMileageKm ?? 0);
  }, 0);
  const latency = Math.round(
    dataSources.reduce((sum, source) => sum + source.latencyP95Ms, 0) / Math.max(dataSources.length, 1)
  );
  return [
    { label: '筛选车辆', value: formatNumber(filteredVehicles.length), sub: `在线 ${online} / 关注 ${warning}`, tone: 'blue' },
    { label: '今日里程', value: `${formatNumber(mileage, 1)} km`, sub: '按当前筛选车辆合计', tone: 'green' },
    { label: '质量事件', value: String(alertEvents.length), sub: '来自 alert-events/summary', tone: 'orange' },
    { label: '数据延迟 P95', value: `${latency} ms`, sub: '来自 source-readiness', tone: 'cyan' }
  ];
}

export function getRealtimeSummary(filteredVehicles: VehicleRecord[]): RealtimeSummaryProjection[] {
  const online = filteredVehicles.filter((vehicle) => vehicle.status === 'online').length;
  const warning = filteredVehicles.filter((vehicle) => vehicle.status === 'warning').length;
  const located = filteredVehicles.filter((vehicle) => {
    const source = getVehiclePrimarySource(vehicle);
    return vehicle.status !== 'offline' && source.status !== 'offline' && vehicle.longitude && vehicle.latitude;
  }).length;
  const multiSource = filteredVehicles.filter((vehicle) => vehicle.sources.length > 1).length;
  const freshWithinMinute = filteredVehicles.filter((vehicle) => sourceLastSeenSeconds(getVehiclePrimarySource(vehicle)) <= 60).length;
  const fieldCount = filteredVehicles.reduce((sum, vehicle) => sum + getVehiclePrimarySource(vehicle).fieldCount, 0);

  return [
    { label: '在线车辆', value: `${online}/${filteredVehicles.length}`, sub: `关注 ${warning} 台 / Redis online`, tone: 'green' },
    { label: '实时位置', value: `${located}`, sub: 'vehicle_realtime_location 可展示', tone: 'blue' },
    { label: '1分钟内上报', value: `${freshWithinMinute}`, sub: '按最近上报时间判断', tone: 'cyan' },
    { label: '主来源字段', value: formatNumber(fieldCount), sub: `${multiSource} 台车具备多路来源`, tone: 'orange' }
  ];
}

function hasBusinessCoordinate(vehicle: VehicleRecord) {
  return (
    Number.isFinite(vehicle.longitude) &&
    Number.isFinite(vehicle.latitude) &&
    vehicle.longitude >= 73 &&
    vehicle.longitude <= 135 &&
    vehicle.latitude >= 18 &&
    vehicle.latitude <= 54
  );
}

function vehicleFlatFieldCount(vehicle: VehicleRecord) {
  return Object.values(vehicle.flatFields).reduce((sum, fields) => sum + Object.keys(fields).length, 0);
}

function priorityDetail(vehiclesWithIssue: VehicleRecord[], emptyText: string) {
  if (vehiclesWithIssue.length === 0) {
    return emptyText;
  }
  const samples = vehiclesWithIssue.slice(0, 2).map((vehicle) => vehicle.plate || vehicle.vin).join(' / ');
  const more = vehiclesWithIssue.length > 2 ? ` 等 ${vehiclesWithIssue.length} 台` : '';
  return `${samples}${more}`;
}

export function getCommandPriorityItems(filteredVehicles: VehicleRecord[]): CommandPriorityItem[] {
  const sourceIssueVehicles = filteredVehicles.filter((vehicle) =>
    vehicle.status !== 'online' ||
    vehicle.sources.some((source) => source.status !== 'online')
  );
  const offlineVehicleCount = sourceIssueVehicles.filter((vehicle) =>
    vehicle.status === 'offline' ||
    vehicle.sources.some((source) => source.status === 'offline')
  ).length;
  const missingCoordinateVehicles = filteredVehicles.filter((vehicle) => !hasBusinessCoordinate(vehicle));
  const missingMileageVehicles = filteredVehicles.filter((vehicle) =>
    vehicle.sources.every((source) => !hasMileageValue(source))
  );
  const weakFieldVehicles = filteredVehicles.filter((vehicle) =>
    vehicleFlatFieldCount(vehicle) === 0 ||
    vehicle.sources.every((source) => source.fieldCount <= 0)
  );

  return [
    {
      key: 'source-health',
      title: '先看来源',
      value: String(sourceIssueVehicles.length),
      detail: priorityDetail(sourceIssueVehicles, '来源在线态正常'),
      tone: offlineVehicleCount > 0 ? 'error' : sourceIssueVehicles.length > 0 ? 'warning' : 'ok',
      vehicleIds: sourceIssueVehicles.map((vehicle) => vehicle.id)
    },
    {
      key: 'coordinate',
      title: '坐标缺失',
      value: String(missingCoordinateVehicles.length),
      detail: priorityDetail(missingCoordinateVehicles, '地图落点完整'),
      tone: missingCoordinateVehicles.length > 0 ? 'warning' : 'ok',
      vehicleIds: missingCoordinateVehicles.map((vehicle) => vehicle.id)
    },
    {
      key: 'mileage',
      title: '里程缺口',
      value: String(missingMileageVehicles.length),
      detail: priorityDetail(missingMileageVehicles, '里程来源完整'),
      tone: missingMileageVehicles.length > 0 ? 'warning' : 'ok',
      vehicleIds: missingMileageVehicles.map((vehicle) => vehicle.id)
    },
    {
      key: 'field-coverage',
      title: '字段覆盖',
      value: String(weakFieldVehicles.length),
      detail: priorityDetail(weakFieldVehicles, '实时字段已覆盖'),
      tone: weakFieldVehicles.length > 0 ? 'warning' : 'ok',
      vehicleIds: weakFieldVehicles.map((vehicle) => vehicle.id)
    }
  ];
}

export function getRealtimeEvidence(vehicle: VehicleRecord, activeSource: VehicleSourceSnapshot): RealtimeEvidenceProjection[] {
  return [
    {
      label: '在线态',
      source: 'Redis vehicle:online / vehicle:last_seen',
      value: `${statusLabel[vehicle.status]} / ${activeSource.lastSeen}`
    },
    {
      label: '实时位置',
      source: 'MySQL vehicle_realtime_location',
      value: `${vehicle.latitude.toFixed(6)}, ${vehicle.longitude.toFixed(6)}`
    },
    {
      label: '实时快照',
      source: 'MySQL vehicle_realtime_snapshot / Redis realtime-raw',
      value: `${activeSource.fieldCount} fields / ${activeSource.sourceName}`
    },
    {
      label: '历史证据',
      source: 'TDengine raw_frames / vehicle_locations',
      value: `${activeSource.protocol} / VIN ${vehicle.vin}`
    }
  ];
}

export function getReplaySummary(points: TrackPoint[]): ReplaySummaryProjection[] {
  const firstPoint = points[0];
  const lastPoint = points[points.length - 1];
  const distanceKm = firstPoint && lastPoint ? Math.max(0, lastPoint.mileageKm - firstPoint.mileageKm) : 0;
  const maxSpeed = points.reduce((max, point) => Math.max(max, point.speedKph), 0);
  const alertCount = points.filter((point) => point.state === 'alert').length;
  const stopCount = points.filter((point) => point.state === 'stop').length;

  return [
    { label: '轨迹节点', value: String(points.length), sub: 'TDengine vehicle_locations', tone: 'blue' },
    { label: '区间里程', value: `${formatNumber(distanceKm, 1)} km`, sub: '末点总里程 - 首点总里程', tone: 'green' },
    { label: '最高速度', value: `${maxSpeed} km/h`, sub: '按位置点速度取最大值', tone: 'cyan' },
    { label: '告警/停留', value: `${alertCount}/${stopCount}`, sub: 'alarm_flag 节点 / stop 节点', tone: 'orange' }
  ];
}

export function getReplaySegments(points: TrackPoint[]): ReplaySegmentProjection[] {
  return points.slice(1).map((point, index) => {
    const previous = points[index];
    return {
      id: `${previous.id}-${point.id}`,
      from: previous.time,
      to: point.time,
      distanceKm: Math.max(0, point.mileageKm - previous.mileageKm),
      speedDeltaKph: point.speedKph - previous.speedKph,
      state: point.state
    };
  });
}

export function getReplayEvidence(vehicle: VehicleRecord, points: TrackPoint[]): ReplayEvidenceProjection[] {
  const firstPoint = points[0];
  const lastPoint = points[points.length - 1];
  const hasAlert = points.some((point) => point.state === 'alert');
  const distanceKm = firstPoint && lastPoint ? Math.max(0, lastPoint.mileageKm - firstPoint.mileageKm) : 0;

  return [
    {
      title: '轨迹点',
      source: 'TDengine vehicle_locations',
      detail: `${vehicle.plate} 当前时间窗 ${points.length} 个位置点，可按 VIN + 时间窗回放。`,
      status: points.length > 0 ? 'ready' : 'warning'
    },
    {
      title: '里程闭合',
      source: 'MySQL vehicle_daily_mileage / TDengine vehicle_locations',
      detail: points.length > 1
        ? `区间里程 ${formatNumber(distanceKm, 1)} km，用首末总里程差校验轨迹里程。`
        : '需要至少两个位置点才能形成区间里程差。',
      status: points.length > 1 ? 'ready' : 'warning'
    },
    {
      title: 'RAW 回溯',
      source: 'TDengine raw_frames / raw_frame_payload_chunks',
      detail: `按 VIN ${vehicle.vin}、协议和节点时间回查原始帧 parsed_fields。`,
      status: 'ready'
    },
    {
      title: '异常节点',
      source: 'BFF alert-events / TDengine vehicle_locations',
      detail: hasAlert ? '轨迹中存在 alert 节点，可与告警中心事件交叉查看。' : '当前时间窗未出现 alert 节点。',
      status: hasAlert ? 'warning' : 'ready'
    }
  ];
}

export function getSourceName(sourceId: string) {
  return dataSources.find((source) => source.id === sourceId)?.name ?? sourceId;
}

export function getAlertRows({
  query,
  protocolFilter,
  sourceFilter
}: {
  query: string;
  protocolFilter: ProtocolFilter;
  sourceFilter: string;
}) {
  return alertEvents.filter((alert) => {
    const vehicle = vehicles.find((item) => item.id === alert.vehicleId);
    const source = dataSources.find((item) => item.id === alert.sourceId);
    const queryMatched = vehicle ? includesQuery(vehicle, query) : true;
    const protocolMatched = protocolFilter === 'ALL' || source?.protocol === protocolFilter;
    const sourceMatched = sourceFilter === 'ALL' || alert.sourceId === sourceFilter;
    return queryMatched && protocolMatched && sourceMatched;
  });
}

export function getAlertSummary(rows: AlertEvent[]): AlertSummaryProjection[] {
  const p1OrAbove = rows.filter((alert) => alert.level === 'P0' || alert.level === 'P1').length;
  const affectedVehicleCount = new Set(rows.map((alert) => alert.vehicleId)).size;
  const sourceCount = new Set(rows.map((alert) => alert.sourceId)).size;

  return [
    { label: '质量事件', value: String(rows.length), sub: '来自 alert-events 查询', tone: 'orange' },
    { label: 'P1 及以上', value: String(p1OrAbove), sub: '用于观测优先级筛选', tone: 'blue' },
    { label: '影响车辆', value: String(affectedVehicleCount), sub: '按 VIN 去重统计', tone: 'green' },
    { label: '涉及来源', value: String(sourceCount), sub: '关联 vehicle_data_source', tone: 'cyan' }
  ];
}

export function getAlertEvidenceItems(alert: AlertEvent): AlertEvidenceProjection[] {
  const vehicle = vehicles.find((item) => item.id === alert.vehicleId);
  const source = dataSources.find((item) => item.id === alert.sourceId);
  const sourceLabel = source?.name ?? alert.sourceId;
  const vehicleLabel = vehicle ? `${vehicle.plate} / ${vehicle.vin}` : alert.vehicleId;
  const latestSource = vehicle?.sources.find((item) => item.id === alert.sourceId);

  const items: AlertEvidenceProjection[] = [
    {
      title: '实时在线态',
      source: 'Redis vehicle:online / vehicle:last_seen',
      detail: latestSource
        ? `${vehicleLabel} 最近上报 ${latestSource.lastSeen}，来源 ${sourceLabel}`
        : `${vehicleLabel} 可按 VIN 查询最近在线状态`,
      status: alert.type === '断链' || alert.type === '定位异常' ? 'warning' : 'ready'
    },
    {
      title: '实时投影',
      source: 'MySQL vehicle_realtime_snapshot / vehicle_realtime_location',
      detail: '只使用实时数据帧投影，登录、鉴权、注册帧不进入车辆实时态。',
      status: alert.type === '字段缺失' ? 'warning' : 'ready'
    },
    {
      title: '历史 RAW',
      source: 'TDengine raw_frames / raw_frame_payload_chunks',
      detail: '按 VIN、协议、时间窗回溯原始帧和 parsed_fields，不在前端二次解析协议。',
      status: 'ready'
    }
  ];

  if (alert.type === '里程跳变') {
    items.push({
      title: '统计口径',
      source: 'MySQL vehicle_daily_mileage',
      detail: '日里程按 VIN、日期、协议独立统计，异常区间由统计服务跳过或降级。',
      status: 'warning'
    });
  } else {
    items.push({
      title: '轨迹证据',
      source: 'TDengine vehicle_locations',
      detail: '按位置点时间序列查看断链、定位异常或字段缺失前后的轨迹变化。',
      status: 'ready'
    });
  }

  return items;
}

export function getDailyMileageRows(protocolFilter: ProtocolFilter = 'ALL'): DailyMileageProjection[] {
  return vehicles.flatMap((vehicle) =>
    vehicle.sources
      .filter((source) => protocolFilter === 'ALL' || source.protocol === protocolFilter)
      .filter((source) => source.todayMileageKm != null || source.totalMileageKm != null)
      .map((source) => ({
        id: `${vehicle.id}-${source.id}`,
        plate: vehicle.plate,
        vin: vehicle.vin,
        protocol: source.protocol,
        sourceName: source.sourceName,
        todayMileageKm: source.todayMileageKm ?? 0,
        totalMileageKm: source.totalMileageKm ?? 0,
        lastSeen: source.lastSeen,
        online: source.status === 'online'
      }))
  );
}

export function getProtocolMileageStats(rows = getDailyMileageRows()): ProtocolMileageProjection[] {
  const protocols: Protocol[] = ['GB32960', 'JT808', 'YUTONG_MQTT'];
  return protocols.map((protocol) => {
    const protocolRows = rows.filter((row) => row.protocol === protocol);
    return {
      protocol,
      label: protocol === 'GB32960' ? 'GB/T 32960' : protocol === 'JT808' ? 'JT/T 808' : '宇通 MQTT',
      vehicleCount: protocolRows.length,
      onlineCount: protocolRows.filter((row) => row.online).length,
      dailyMileageKm: protocolRows.reduce((sum, row) => sum + row.todayMileageKm, 0),
      latestTotalMileageKm: protocolRows.reduce((sum, row) => sum + row.totalMileageKm, 0),
      rowCount: protocolRows.length
    };
  });
}

export function buildHistoryQueryPreview({
  keyword,
  protocolFilter,
  selectedFields,
  dateFrom,
  dateTo,
  limit,
  offset
}: {
  keyword: string;
  protocolFilter: ProtocolFilter;
  selectedFields: string[];
  dateFrom: string;
  dateTo: string;
  limit: number;
  offset: number;
}): HistoryQueryPreview {
  return {
    endpoint: '/api/history/raw-frames/query',
    method: 'POST',
    storage: 'TDengine raw_frames / raw_frame_payload_chunks',
    payload: {
      ...(keyword.trim() ? { vin: keyword.trim() } : {}),
      ...(protocolFilter !== 'ALL' ? { protocol: protocolFilter } : {}),
      dateFrom,
      dateTo,
      fields: expandRawFrameFields(selectedFields),
      includeFields: true,
      limit,
      offset
    }
  };
}

function absoluteLastSeenSeconds(lastSeenAt?: string) {
  if (!lastSeenAt) {
    return undefined;
  }
  const normalized = lastSeenAt.includes('T') ? lastSeenAt : `${lastSeenAt.replace(' ', 'T')}+08:00`;
  const timestamp = new Date(normalized).getTime();
  if (!Number.isFinite(timestamp)) {
    return undefined;
  }
  return Math.max(0, Math.round((Date.now() - timestamp) / 1000));
}

function lastSeenSeconds(lastSeen: string) {
  if (!/[秒分钟小时]/.test(lastSeen)) {
    const absoluteSeconds = absoluteLastSeenSeconds(lastSeen);
    if (absoluteSeconds != null) {
      return absoluteSeconds;
    }
  }
  const match = lastSeen.match(/(\d+)/);
  if (!match) {
    return Number.MAX_SAFE_INTEGER;
  }
  const value = Number(match[1]);
  if (lastSeen.includes('分钟')) {
    return value * 60;
  }
  if (lastSeen.includes('小时')) {
    return value * 3600;
  }
  return value;
}

function sourceLastSeenSeconds(source: VehicleSourceSnapshot) {
  return absoluteLastSeenSeconds(source.lastSeenAt) ?? lastSeenSeconds(source.lastSeen);
}

function hasMileageValue(source: VehicleSourceSnapshot) {
  return (source.totalMileageKm ?? 0) > 0 || (source.todayMileageKm ?? 0) > 0;
}

function mileageDecisionStatus({
  candidateCount,
  selected,
  mileageDeltaKm
}: {
  candidateCount: number;
  selected?: VehicleSourceSnapshot;
  mileageDeltaKm: number;
}): MileageSourceDecisionStatus {
  if (candidateCount <= 0) {
    return 'missing';
  }
  if (mileageDeltaKm > 5) {
    return 'conflict';
  }
  if (!selected || selected.status !== 'online') {
    return 'warning';
  }
  return 'ready';
}

export function getMileageSourceDecisions(
  records: VehicleRecord[],
  protocolFilter: ProtocolFilter = 'ALL'
): MileageSourceDecision[] {
  return records.map((vehicle) => {
    const scopedSources = vehicle.sources.filter((source) => protocolFilter === 'ALL' || source.protocol === protocolFilter);
    const candidates = scopedSources.filter(hasMileageValue);
    const selected = candidates.slice().sort((left, right) => {
      const onlineDelta = Number(right.status === 'online') - Number(left.status === 'online');
      if (onlineDelta !== 0) return onlineDelta;
      const freshnessDelta = sourceLastSeenSeconds(left) - sourceLastSeenSeconds(right);
      if (freshnessDelta !== 0) return freshnessDelta;
      return left.latencyMs - right.latencyMs;
    })[0];
    const mileageValues = candidates.map((source) => source.totalMileageKm ?? 0).filter((value) => value > 0);
    const mileageDeltaKm = mileageValues.length > 1 ? Math.max(...mileageValues) - Math.min(...mileageValues) : 0;
    const status = mileageDecisionStatus({
      candidateCount: candidates.length,
      selected,
      mileageDeltaKm
    });
    const selectedDailyMileageKm = selected?.todayMileageKm ?? 0;
    const selectedTotalMileageKm = selected?.totalMileageKm ?? 0;
    const statusTitle: Record<MileageSourceDecisionStatus, string> = {
      ready: '可信来源可用',
      warning: '候选来源需关注',
      conflict: '多源里程冲突',
      missing: '缺少里程来源'
    };
    const detail: Record<MileageSourceDecisionStatus, string> = {
      ready: `展示口径选择 ${selected?.sourceName ?? '--'}，不把多协议里程相加。`,
      warning: `存在 ${candidates.length} 个里程候选，但当前选择来源非在线或证据不足。`,
      conflict: `多个来源总里程差异 ${formatNumber(mileageDeltaKm, 1)} km，查询层应保留候选并人工确认可信来源。`,
      missing: '当前筛选来源没有有效总里程或日里程，不展示里程统计值。'
    };

    return {
      id: `${vehicle.id}-mileage-decision`,
      plate: vehicle.plate,
      vin: vehicle.vin,
      selectedProtocol: selected?.protocol,
      selectedSourceName: selected?.sourceName ?? '--',
      selectedDailyMileageKm,
      selectedTotalMileageKm,
      candidateCount: candidates.length,
      sourceCount: scopedSources.length,
      status,
      title: statusTitle[status],
      detail: detail[status],
      sources: scopedSources.map((source) => ({
        id: source.id,
        protocol: source.protocol,
        sourceName: source.sourceName,
        todayMileageKm: source.todayMileageKm ?? 0,
        totalMileageKm: source.totalMileageKm ?? 0,
        online: source.status === 'online',
        lastSeen: source.lastSeen
      }))
    };
  });
}

export function getSourceConsistency(vehicle: VehicleRecord): SourceConsistencyProjection {
  const canonicalProtocols: Protocol[] = ['GB32960', 'JT808', 'YUTONG_MQTT'];
  const protocols = getVehicleProtocols(vehicle);
  const sourceCount = vehicle.sources.length;
  const onlineSourceCount = vehicle.sources.filter((source) => source.status === 'online').length;
  const locatedSourceCount = vehicle.sources.filter((source) => source.speedKph != null || source.totalMileageKm != null).length;
  const missingProtocols = canonicalProtocols.filter((protocol) => !protocols.includes(protocol));
  const mileageValues = vehicle.sources
    .map((source) => source.totalMileageKm ?? 0)
    .filter((value) => value > 0);
  const mileageDeltaKm = mileageValues.length > 1 ? Math.max(...mileageValues) - Math.min(...mileageValues) : 0;
  const timeValues = vehicle.sources.map(sourceLastSeenSeconds).filter((value) => Number.isFinite(value));
  const sourceTimeDeltaSeconds = timeValues.length > 1 ? Math.max(...timeValues) - Math.min(...timeValues) : 0;
  const base = { sourceCount, onlineSourceCount, locatedSourceCount, missingProtocols, mileageDeltaKm, sourceTimeDeltaSeconds };
  if (sourceCount <= 0) {
    return { ...base, severity: 'warning', title: '暂无来源', detail: '当前车辆没有可用于交叉校验的数据来源。' };
  }
  if (sourceCount === 1) {
    return { ...base, severity: 'warning', title: '单一来源', detail: '当前车辆只有一个数据来源，无法做跨来源一致性校验。' };
  }
  if (onlineSourceCount <= 0) {
    return { ...base, severity: 'error', title: '全部来源离线', detail: `${onlineSourceCount}/${sourceCount} 个来源在线，无法判断实时一致性。` };
  }
  if (missingProtocols.length > 0 || onlineSourceCount < sourceCount) {
    return { ...base, severity: 'warning', title: '来源不完整', detail: `${onlineSourceCount}/${sourceCount} 个来源在线，车辆服务可用但需要关注缺失或离线来源。` };
  }
  if (mileageDeltaKm > 5) {
    return { ...base, severity: 'warning', title: '里程差异偏大', detail: '多个来源的实时总里程差异超过 5 km，请核对来源解析、单位和上报时间。' };
  }
  if (sourceTimeDeltaSeconds > 300) {
    return { ...base, severity: 'warning', title: '来源时间差异', detail: '多个来源的最近上报时间差异超过 5 分钟，请关注链路延迟或转发中断。' };
  }
  return { ...base, severity: 'ok', title: '来源一致', detail: `${onlineSourceCount}/${sourceCount} 个来源在线，里程和时间差异在可接受范围内。` };
}
