import { getApiBaseUrl, shouldUsePrototypeRealData } from '../config/appConfig';
import { isValidAMapCoordinate, reverseGeocodeWithAMap } from '../integrations/amap';
import { expandRawFrameFields } from './rawFieldMap';
import type {
  DataSourceRecord,
  Protocol,
  SourceHealth,
  TrackPoint,
  VehicleRecord,
  VehicleSourceSnapshot,
  VehicleStatus
} from './types';

type RawRealtimeLocation = Record<string, unknown> & {
  protocol?: string;
  vin?: string;
  plate?: string;
  event_id?: string;
  longitude?: number;
  latitude?: number;
  altitude_m?: number;
  direction_deg?: number;
  speedKmh?: number;
  speed_kmh?: number;
  socPercent?: number;
  soc_percent?: number;
  totalMileageKm?: number;
  total_mileage_km?: number;
  alarm_flag?: number;
  status_flag?: number;
  lastSeen?: string;
  updated_at?: string;
  received_at?: string;
  event_time?: string;
};

type RawRealtimeSnapshot = Record<string, unknown> & {
  protocol?: string;
  vin?: string;
  plate?: string;
  platform_name?: string;
  peer?: string;
  parsed_json?: unknown;
  parsed_fields?: unknown;
  event_time?: string;
  received_at?: string;
  event_id?: string;
  updated_at?: string;
};

type RealDataPayload<T> = {
  data?: {
    items?: T[];
    total?: number;
  };
  items?: T[];
  total?: number;
};

type EnvelopePayload<T> = {
  data?: T;
};

type RawMapReverseGeocode = Record<string, unknown> & {
  provider?: string;
  longitude?: number;
  latitude?: number;
  formattedAddress?: string;
  province?: string;
  city?: string;
  district?: string;
  township?: string;
  adcode?: string;
};

type RawFrameApiRow = Record<string, unknown> & {
  frame_id?: string;
  id?: string;
  event_id?: string;
  protocol?: string;
  vin?: string;
  message_id?: number | string;
  message_id_hex?: string;
  command?: string;
  command_hex?: string;
  event_type?: string;
  source_endpoint?: string;
  endpoint?: string;
  topic?: string;
  platform_name?: string;
  received_at?: string;
  event_time?: string;
  device_time?: string;
  raw_size_bytes?: number;
  parsed_fields?: unknown;
};

type RawHistoryLocation = Record<string, unknown> & {
  ts?: string;
  event_id?: string;
  received_at?: string;
  longitude?: number;
  latitude?: number;
  altitude_m?: number;
  speed_kmh?: number;
  direction_deg?: number;
  alarm_flag?: number;
  status_flag?: number;
  total_mileage_km?: number;
  protocol?: string;
  vin?: string;
};

export type PrototypeRealDataState = {
  vehicles: VehicleRecord[];
  status: 'disabled' | 'loading' | 'ready' | 'stale' | 'fallback' | 'error';
  source: string;
  fetchedAt?: string;
  error?: string;
  total?: number;
};

export type PrototypeTrackState = {
  points: TrackPoint[];
  status: 'disabled' | 'loading' | 'ready' | 'empty' | 'error';
  source: string;
  fetchedAt?: string;
  error?: string;
  total: number;
};

export type PrototypeTrackQuery = {
  dateFrom?: string;
  dateTo?: string;
  limit?: number;
};

export type PrototypeSourceMonitorState = {
  records: DataSourceRecord[];
  protocolEvidence: Array<{
    protocol: Protocol;
    realtimeRows: number;
    rawRows: number;
    endpointCount: number;
    missingEndpointRows: number;
    healthyEndpoints: number;
    warningEndpoints: number;
    downEndpoints: number;
    lastFrameAt: string;
  }>;
  status: 'disabled' | 'loading' | 'ready' | 'partial' | 'empty' | 'error';
  source: string;
  fetchedAt?: string;
  error?: string;
  realtimeRows: number;
  rawRows: number;
  rawEndpointCount: number;
  missingEndpointCount: number;
};

export type PrototypeAlertSignal = {
  id: string;
  level: 'P1' | 'P2';
  type: '能力缺口' | '链路延迟' | '字段缺失' | '协议告警位';
  title: string;
  detail: string;
  protocol?: Protocol;
  vin?: string;
  plate?: string;
  time: string;
  source: string;
  longitude?: number;
  latitude?: number;
  speedKph?: number;
  totalMileageKm?: number;
  evidence: Array<{
    label: string;
    value: string;
  }>;
};

export type PrototypeAlertSignalState = {
  signals: PrototypeAlertSignal[];
  status: 'disabled' | 'loading' | 'ready' | 'partial' | 'empty' | 'error';
  source: string;
  fetchedAt?: string;
  error?: string;
  realtimeRows: number;
  historyRows: number;
  durableAlertApi: 'unavailable' | 'unknown';
};

export type PrototypeRawFrameRecord = {
  id: string;
  protocol: Protocol;
  vin: string;
  frameType: string;
  receiveTime: string;
  deviceTime: string;
  sourceName: string;
  latencyMs: number;
  fieldCount: number;
  rawSizeBytes: number;
  parsedFields: Record<string, unknown>;
};

export type PrototypeRawFrameQuery = {
  vin?: string;
  protocol?: Protocol;
  fields?: string[];
  dateFrom?: string;
  dateTo?: string;
  limit: number;
  offset: number;
};

export type PrototypeRawFrameState = {
  records: PrototypeRawFrameRecord[];
  status: 'disabled' | 'loading' | 'ready' | 'empty' | 'error';
  source: string;
  fetchedAt?: string;
  error?: string;
  total: number;
  limit: number;
  offset: number;
};

export type PrototypeMapAddressState = {
  status: 'disabled' | 'loading' | 'ready' | 'unavailable' | 'error';
  source: string;
  fetchedAt?: string;
  address?: string;
  provider?: string;
  region?: string;
  error?: string;
};

const protocolLabels: Record<Protocol, string> = {
  GB32960: 'GB/T 32960 实时接入',
  JT808: 'JT/T 808 实时接入',
  YUTONG_MQTT: '宇通 MQTT 实时接入'
};

const protocolShortLabels: Record<Protocol, string> = {
  GB32960: 'GB32960',
  JT808: 'JT808',
  YUTONG_MQTT: '宇通 MQTT'
};

const protocolPrefixes: Record<Protocol, string> = {
  GB32960: 'gb32960.realtime',
  JT808: 'jt808.location',
  YUTONG_MQTT: 'mqtt.realtime'
};

const protocolSet = new Set<Protocol>(['GB32960', 'JT808', 'YUTONG_MQTT']);
const protocolOrder: Protocol[] = ['GB32960', 'JT808', 'YUTONG_MQTT'];
const mapAddressCacheTtlMs = 5 * 60 * 1000;
const mapAddressCache = new Map<string, { expiresAt: number; state: PrototypeMapAddressState }>();
const realtimeSessionCacheKey = 'lingniu:vehicle-platform:prototype:realtime:v1';

function apiURL(path: string) {
  const base = getApiBaseUrl();
  return base ? `${base}${path}` : path;
}

function asProtocol(value: unknown): Protocol | undefined {
  const normalized = String(value ?? '').trim();
  return protocolSet.has(normalized as Protocol) ? (normalized as Protocol) : undefined;
}

function numberValue(...values: unknown[]) {
  for (const value of values) {
    const number = Number(value);
    if (Number.isFinite(number)) {
      return number;
    }
  }
  return 0;
}

function optionalNumber(...values: unknown[]) {
  for (const value of values) {
    if (value == null || value === '') {
      continue;
    }
    const number = Number(value);
    if (Number.isFinite(number)) {
      return number;
    }
  }
  return undefined;
}

function stringValue(...values: unknown[]) {
  for (const value of values) {
    const next = String(value ?? '').trim();
    if (next) {
      return next;
    }
  }
  return '';
}

function parseChinaTime(value: string) {
  if (!value) {
    return NaN;
  }
  const normalized = value.includes('T') ? value : `${value.replace(' ', 'T')}+08:00`;
  return new Date(normalized).getTime();
}

function formatChinaTime(value: string) {
  if (!value) {
    return '--';
  }
  const timestamp = parseChinaTime(value);
  if (!Number.isFinite(timestamp)) {
    return value;
  }
  return new Date(timestamp).toLocaleString('zh-CN', {
    hour12: false,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
}

function formatClockTime(value: string) {
  const timestamp = parseChinaTime(value);
  if (!Number.isFinite(timestamp)) {
    return value || '--';
  }
  return new Date(timestamp).toLocaleTimeString('zh-CN', {
    hour12: false,
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
}

function relativeLastSeen(value: string) {
  const timestamp = parseChinaTime(value);
  if (!Number.isFinite(timestamp)) {
    return value || '--';
  }
  const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
  if (seconds < 60) {
    return `${seconds} 秒前`;
  }
  if (seconds < 3600) {
    return `${Math.round(seconds / 60)} 分钟前`;
  }
  return value;
}

function statusFromLastSeen(value: string): VehicleStatus {
  const timestamp = parseChinaTime(value);
  if (!Number.isFinite(timestamp)) {
    return 'warning';
  }
  const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
  if (seconds <= 90) {
    return 'online';
  }
  return seconds <= 600 ? 'warning' : 'offline';
}

function latencyMs(row: RawRealtimeLocation) {
  const received = parseChinaTime(stringValue(row.received_at, row.updated_at, row.lastSeen));
  const event = parseChinaTime(stringValue(row.event_time, row.lastSeen, row.updated_at));
  if (!Number.isFinite(received) || !Number.isFinite(event)) {
    return 0;
  }
  return Math.max(0, Math.round(received - event));
}

function rawFrameLatencyMs(row: RawFrameApiRow) {
  const received = parseChinaTime(stringValue(row.received_at));
  const event = parseChinaTime(stringValue(row.event_time, row.device_time));
  if (!Number.isFinite(received) || !Number.isFinite(event)) {
    return 0;
  }
  return Math.max(0, Math.round(received - event));
}

function realtimeLatencyMs(row: RawRealtimeLocation) {
  const received = parseChinaTime(stringValue(row.received_at, row.updated_at));
  const event = parseChinaTime(stringValue(row.event_time));
  if (!Number.isFinite(received) || !Number.isFinite(event)) {
    return 0;
  }
  return Math.max(0, Math.round(received - event));
}

function fieldValueAllowed(value: unknown) {
  return value == null || ['string', 'number', 'boolean'].includes(typeof value);
}

function flatFieldValue(value: unknown): string | number | boolean | null | undefined {
  if (fieldValueAllowed(value)) {
    return value as string | number | boolean | null;
  }
  if (Array.isArray(value)) {
    return JSON.stringify(value);
  }
  return undefined;
}

function flattenObjectFields(value: unknown, prefix = ''): VehicleRecord['flatFields'][Protocol] {
  const fields: VehicleRecord['flatFields'][Protocol] = {};
  const primitive = flatFieldValue(value);
  if (primitive !== undefined && prefix) {
    fields[prefix] = primitive;
    return fields;
  }
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return fields;
  }
  Object.entries(value as Record<string, unknown>).forEach(([key, nestedValue]) => {
    const nestedKey = prefix ? `${prefix}.${key}` : key;
    const nestedPrimitive = flatFieldValue(nestedValue);
    if (nestedPrimitive !== undefined) {
      fields[nestedKey] = nestedPrimitive;
      return;
    }
    Object.assign(fields, flattenObjectFields(nestedValue, nestedKey));
  });
  return fields;
}

function flattenRealtimeFields(
  protocol: Protocol,
  row: RawRealtimeLocation,
  parsedFields: VehicleRecord['flatFields'][Protocol] = {}
) {
  const fields: VehicleRecord['flatFields'][Protocol] = { ...parsedFields };
  const prefix = protocolPrefixes[protocol];
  Object.entries(row).forEach(([key, value]) => {
    if (key === 'protocol' || key === 'vin' || key === 'plate' || !fieldValueAllowed(value)) {
      return;
    }
    fields[`${prefix}.${key}`] = value as string | number | boolean | null;
  });
  return fields;
}

function sourceFromRow(protocol: Protocol, row: RawRealtimeLocation, fields: Record<string, unknown>): VehicleSourceSnapshot {
  const lastSeen = stringValue(row.lastSeen, row.updated_at, row.received_at, row.event_time);
  return {
    id: `real-${protocol}`,
    protocol,
    sourceName: protocolLabels[protocol],
    endpoint: stringValue(row.source_endpoint, row.endpoint, row.topic, '/api/realtime/locations'),
    platformAccount: stringValue(row.platform_name, row.platformAccount, protocol),
    status: statusFromLastSeen(lastSeen),
    latencyMs: latencyMs(row),
    lastSeen: relativeLastSeen(lastSeen),
    lastSeenAt: lastSeen,
    totalMileageKm: numberValue(row.totalMileageKm, row.total_mileage_km),
    speedKph: numberValue(row.speedKmh, row.speed_kmh),
    fieldCount: Object.keys(fields).length
  };
}

function mapXFromLongitude(longitude: number) {
  return Math.min(88, Math.max(10, ((longitude - 73) / (135 - 73)) * 100));
}

function mapYFromLatitude(latitude: number) {
  return Math.min(82, Math.max(12, ((54 - latitude) / (54 - 18)) * 100));
}

function mergeStatus(current: VehicleStatus, next: VehicleStatus): VehicleStatus {
  if (current === 'online' || next === 'online') {
    return 'online';
  }
  if (current === 'warning' || next === 'warning') {
    return 'warning';
  }
  return 'offline';
}

function rowIdentity(row: RawRealtimeLocation, index: number) {
  const vin = stringValue(row.vin);
  if (vin) {
    return vin;
  }
  return stringValue(row.plate, row.phone, `real-vehicle-${index + 1}`);
}

function rowTime(row: RawRealtimeLocation) {
  return parseChinaTime(stringValue(row.updated_at, row.lastSeen, row.received_at, row.event_time));
}

function vehicleFromRow(
  row: RawRealtimeLocation,
  protocol: Protocol,
  index: number,
  parsedFields?: VehicleRecord['flatFields'][Protocol]
): VehicleRecord {
  const longitude = numberValue(row.longitude);
  const latitude = numberValue(row.latitude);
  const fields = flattenRealtimeFields(protocol, row, parsedFields);
  const source = sourceFromRow(protocol, row, fields);
  const vin = rowIdentity(row, index);
  const plate = stringValue(row.plate, row.vehicle_no, vin);
  return {
    id: `real-${vin}`,
    plate,
    vin,
    phone: stringValue(row.phone),
    oem: stringValue(row.oem, row.platform_name, protocolLabels[protocol]),
    status: source.status,
    city: stringValue(row.city, '实时坐标'),
    address: stringValue(row.location_text, row.address, `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`),
    longitude,
    latitude,
    mapX: mapXFromLongitude(longitude),
    mapY: mapYFromLatitude(latitude),
    heading: numberValue(row.directionDeg, row.direction_deg),
    soc: numberValue(row.socPercent, row.soc_percent) || undefined,
    primarySourceId: source.id,
    sources: [source],
    flatFields: {
      GB32960: protocol === 'GB32960' ? fields : {},
      JT808: protocol === 'JT808' ? fields : {},
      YUTONG_MQTT: protocol === 'YUTONG_MQTT' ? fields : {}
    }
  };
}

function mergeVehicle(
  base: VehicleRecord,
  row: RawRealtimeLocation,
  protocol: Protocol,
  parsedFields?: VehicleRecord['flatFields'][Protocol]
) {
  const fields = flattenRealtimeFields(protocol, row, parsedFields);
  const source = sourceFromRow(protocol, row, fields);
  const currentPrimary = base.sources.find((item) => item.id === base.primarySourceId);
  const nextTime = rowTime(row);
  const currentTime = parseChinaTime(currentPrimary?.lastSeenAt ?? currentPrimary?.lastSeen ?? '');
  const sourceIndex = base.sources.findIndex((item) => item.protocol === protocol);
  if (sourceIndex >= 0) {
    base.sources[sourceIndex] = source;
  } else {
    base.sources.push(source);
  }
  base.flatFields[protocol] = {
    ...base.flatFields[protocol],
    ...fields
  };
  base.status = mergeStatus(base.status, source.status);
  if (!Number.isFinite(currentTime) || nextTime >= currentTime) {
    base.primarySourceId = source.id;
    base.longitude = numberValue(row.longitude, base.longitude);
    base.latitude = numberValue(row.latitude, base.latitude);
    base.heading = numberValue(row.directionDeg, row.direction_deg, base.heading);
    base.address = stringValue(row.location_text, row.address, `${base.latitude.toFixed(6)}, ${base.longitude.toFixed(6)}`);
  }
  base.plate = stringValue(base.plate, row.plate, base.vin);
  base.soc = numberValue(row.socPercent, row.soc_percent, base.soc) || base.soc;
}

function toVehicles(rows: RawRealtimeLocation[]) {
  const grouped = new Map<string, VehicleRecord>();
  rows.forEach((row, index) => {
    const protocol = asProtocol(row.protocol);
    if (!protocol) {
      return;
    }
    const identity = rowIdentity(row, index);
    const current = grouped.get(identity);
    if (current) {
      mergeVehicle(current, row, protocol);
    } else {
      grouped.set(identity, vehicleFromRow(row, protocol, index));
    }
  });
  return Array.from(grouped.values());
}

function parsedFieldsObject(value: unknown): Record<string, unknown> {
  if (value && typeof value === 'object' && !Array.isArray(value)) {
    return value as Record<string, unknown>;
  }
  if (typeof value === 'string') {
    try {
      const parsed = JSON.parse(value);
      if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
        return parsed as Record<string, unknown>;
      }
    } catch {
      return {};
    }
  }
  return {};
}

function snapshotParsedFields(row: RawRealtimeSnapshot) {
  return flattenObjectFields(parsedFieldsObject(row.parsed_json ?? row.parsed_fields));
}

function fieldNumber(fields: Record<string, unknown>, ...keys: string[]) {
  return optionalNumber(...keys.map((key) => fields[key]));
}

function yutongTotalMileageKm(fields: Record<string, unknown>) {
  const explicitKm = fieldNumber(fields, 'yutong_mqtt.data.total_mileage_km', 'yutong_mqtt.root.data.total_mileage_km');
  if (explicitKm != null) {
    return explicitKm;
  }
  const meters = fieldNumber(fields, 'yutong_mqtt.data.total_mileage', 'yutong_mqtt.root.data.total_mileage');
  return meters == null ? undefined : meters / 1000;
}

function snapshotLocationRow(row: RawRealtimeSnapshot, protocol: Protocol, fields: Record<string, unknown>): RawRealtimeLocation {
  const gb32960 = protocol === 'GB32960';
  const jt808 = protocol === 'JT808';
  const yutong = protocol === 'YUTONG_MQTT';
  return {
    protocol,
    vin: stringValue(row.vin),
    plate: stringValue(row.plate),
    platform_name: stringValue(row.platform_name),
    source_endpoint: stringValue(row.peer),
    event_id: stringValue(row.event_id),
    event_time: stringValue(row.event_time),
    received_at: stringValue(row.received_at),
    updated_at: stringValue(row.updated_at),
    longitude: fieldNumber(
      fields,
      gb32960 ? 'gb32960.position.longitude' : '',
      jt808 ? 'jt808.location.longitude' : '',
      yutong ? 'yutong_mqtt.data.longitude' : ''
    ),
    latitude: fieldNumber(
      fields,
      gb32960 ? 'gb32960.position.latitude' : '',
      jt808 ? 'jt808.location.latitude' : '',
      yutong ? 'yutong_mqtt.data.latitude' : ''
    ),
    speed_kmh: fieldNumber(
      fields,
      gb32960 ? 'gb32960.vehicle.speed_kmh' : '',
      jt808 ? 'jt808.location.speed_kmh' : '',
      yutong ? 'yutong_mqtt.data.meter_speed' : ''
    ),
    total_mileage_km: fieldNumber(
      fields,
      gb32960 ? 'gb32960.vehicle.total_mileage_km' : '',
      jt808 ? 'jt808.location.total_mileage_km' : '',
      yutong ? 'yutong_mqtt.data.total_mileage_km' : ''
    ) ?? (yutong ? yutongTotalMileageKm(fields) : undefined),
    soc_percent: fieldNumber(
      fields,
      gb32960 ? 'gb32960.vehicle.soc_percent' : '',
      yutong ? 'yutong_mqtt.data.battery_capacity_soc' : ''
    )
  };
}

function mergeRealtimeSnapshots(vehicles: VehicleRecord[], rows: RawRealtimeSnapshot[]) {
  const grouped = new Map<string, VehicleRecord>();
  vehicles.forEach((vehicle) => {
    grouped.set(vehicle.vin || vehicle.plate || vehicle.id, vehicle);
  });
  rows.forEach((row, index) => {
    const protocol = asProtocol(row.protocol);
    if (!protocol) {
      return;
    }
    const fields = snapshotParsedFields(row);
    const locationRow = snapshotLocationRow(row, protocol, fields);
    const identity = rowIdentity(locationRow, vehicles.length + index);
    const current = grouped.get(identity);
    if (current) {
      mergeVehicle(current, locationRow, protocol, fields);
    } else {
      grouped.set(identity, vehicleFromRow(locationRow, protocol, vehicles.length + index, fields));
    }
  });
  return Array.from(grouped.values());
}

function payloadItems<T>(payload: RealDataPayload<T>) {
  return payload.data?.items ?? payload.items ?? [];
}

function payloadTotal<T>(payload: RealDataPayload<T>) {
  return payload.data?.total ?? payload.total;
}

function payloadData<T>(payload: EnvelopePayload<T> | T) {
  if (payload && typeof payload === 'object' && 'data' in payload) {
    return (payload as EnvelopePayload<T>).data;
  }
  return payload as T;
}

function compactErrorBody(value: string) {
  return value.replace(/\s+/g, ' ').trim().slice(0, 240);
}

export async function httpErrorMessage(response: Response) {
  let body = '';
  try {
    body = compactErrorBody(await response.text());
  } catch {
    body = '';
  }
  return body ? `HTTP ${response.status}: ${body}` : `HTTP ${response.status}: empty response body`;
}

async function fetchJSON<T>(path: string, timeoutMs = 5000, init?: RequestInit): Promise<T> {
  const controller = new AbortController();
  let timedOut = false;
  const timer = window.setTimeout(() => {
    timedOut = true;
    controller.abort();
  }, timeoutMs);
  try {
    const response = await fetch(apiURL(path), { ...init, signal: controller.signal });
    if (!response.ok) {
      throw new Error(await httpErrorMessage(response));
    }
    return (await response.json()) as T;
  } catch (error) {
    if (timedOut || (error instanceof DOMException && error.name === 'AbortError')) {
      throw new Error(`request timeout after ${timeoutMs}ms`);
    }
    throw error;
  } finally {
    window.clearTimeout(timer);
  }
}

function mapAddressCacheKey(longitude: number, latitude: number) {
  return `${longitude.toFixed(5)},${latitude.toFixed(5)}`;
}

function cachedMapAddressState(key: string) {
  const cached = mapAddressCache.get(key);
  if (!cached) {
    return undefined;
  }
  if (cached.expiresAt <= Date.now()) {
    mapAddressCache.delete(key);
    return undefined;
  }
  return {
    ...cached.state,
    source: `${cached.state.source} / cache`
  };
}

function cacheMapAddressState(key: string, state: PrototypeMapAddressState) {
  if (state.status !== 'ready') {
    return state;
  }
  mapAddressCache.set(key, {
    expiresAt: Date.now() + mapAddressCacheTtlMs,
    state
  });
  return state;
}

export async function loadPrototypeMapAddress(longitude: number, latitude: number): Promise<PrototypeMapAddressState> {
  if (!shouldUsePrototypeRealData()) {
    return {
      status: 'disabled',
      source: '本地样例',
      error: 'prototype real data disabled'
    };
  }
  if (!isValidAMapCoordinate(longitude, latitude)) {
    return {
      status: 'unavailable',
      source: '/api/map/reverse-geocode',
      error: 'coordinate outside China business bounds'
    };
  }
  const cacheKey = mapAddressCacheKey(longitude, latitude);
  const cached = cachedMapAddressState(cacheKey);
  if (cached) {
    return cached;
  }
  const params = new URLSearchParams({
    longitude: longitude.toFixed(6),
    latitude: latitude.toFixed(6)
  });
  const path = `/api/map/reverse-geocode?${params.toString()}`;
  const fallbackToAMap = async (reason: string): Promise<PrototypeMapAddressState> => {
    try {
      const data = await reverseGeocodeWithAMap(longitude, latitude);
      return cacheMapAddressState(cacheKey, {
        status: 'ready',
        source: `${apiURL(path)} -> AMap Web JS Geocoder`,
        fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
        address: data.formattedAddress,
        provider: data.provider,
        region: [data.province, data.city, data.district, data.township].filter(Boolean).join(' / '),
        error: reason
      });
    } catch (fallbackError) {
      const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
      return {
        status: 'unavailable',
        source: `${apiURL(path)} -> AMap Web JS Geocoder`,
        error: `${reason}; ${fallbackMessage}`
      };
    }
  };
  try {
    const payload = await fetchJSON<EnvelopePayload<RawMapReverseGeocode> | RawMapReverseGeocode>(path, 4500);
    const data = payloadData<RawMapReverseGeocode>(payload);
    const address = stringValue(data?.formattedAddress);
    if (!address) {
      return fallbackToAMap('reverse geocode returned empty address');
    }
    return cacheMapAddressState(cacheKey, {
      status: 'ready',
      source: apiURL(path),
      fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
      address,
      provider: stringValue(data?.provider, 'AMap'),
      region: [data?.province, data?.city, data?.district, data?.township].map((value) => stringValue(value)).filter(Boolean).join(' / ')
    });
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    return fallbackToAMap(message);
  }
}

function rawFrameId(row: RawFrameApiRow, index: number) {
  return stringValue(row.frame_id, row.id, row.event_id, `raw-frame-${index + 1}`);
}

function rawFrameType(row: RawFrameApiRow, protocol: Protocol) {
  const marker = stringValue(row.message_id_hex, row.command_hex, row.command, row.event_type, row.message_id);
  if (protocol === 'GB32960' && (marker === '0x0002' || marker === '2')) {
    return '实时信息上报 0x02';
  }
  if (protocol === 'GB32960' && (marker === '0x0003' || marker === '3')) {
    return '补发信息上报 0x03';
  }
  if (protocol === 'JT808' && marker) {
    return marker.includes('0200') ? '位置信息汇报 0x0200' : `JT808 ${marker}`;
  }
  if (protocol === 'YUTONG_MQTT') {
    return marker ? `MQTT 遥测 ${marker}` : 'MQTT 遥测';
  }
  return marker || 'RAW';
}

function rawFrameFromRow(row: RawFrameApiRow, index: number): PrototypeRawFrameRecord | undefined {
  const protocol = asProtocol(row.protocol);
  if (!protocol) {
    return undefined;
  }
  const parsedFields = parsedFieldsObject(row.parsed_fields);
  return {
    id: rawFrameId(row, index),
    protocol,
    vin: stringValue(row.vin, row.phone, 'unknown'),
    frameType: rawFrameType(row, protocol),
    receiveTime: formatChinaTime(stringValue(row.received_at)),
    deviceTime: formatChinaTime(stringValue(row.event_time, row.device_time)),
    sourceName: stringValue(row.platform_name, row.source_endpoint, row.endpoint, row.topic, protocolLabels[protocol]),
    latencyMs: rawFrameLatencyMs(row),
    fieldCount: Object.keys(parsedFields).length,
    rawSizeBytes: numberValue(row.raw_size_bytes),
    parsedFields
  };
}

function percentile(values: number[], ratio: number) {
  if (values.length === 0) {
    return 0;
  }
  const sorted = values.slice().sort((a, b) => a - b);
  const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * ratio) - 1));
  return sorted[index];
}

function sourceHealthFromLastFrame(value: string): SourceHealth {
  const timestamp = parseChinaTime(value);
  if (!Number.isFinite(timestamp)) {
    return 'warning';
  }
  const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000));
  if (seconds <= 90) {
    return 'healthy';
  }
  return seconds <= 600 ? 'warning' : 'down';
}

function sourceMonitorRecordFromRaw(
  protocol: Protocol,
  endpoint: string,
  rows: RawFrameApiRow[],
  realtimeProtocolCount: number
): DataSourceRecord {
  const lastFrameAt = rows
    .map((row) => stringValue(row.received_at, row.event_time))
    .sort((a, b) => parseChinaTime(b) - parseChinaTime(a))[0] ?? '';
  const uniqueVehicles = new Set(rows.map((row) => stringValue(row.vin, row.phone)).filter(Boolean));
  return {
    id: `real-source-${protocol}-${endpoint}`,
    name: `${protocolShortLabels[protocol]} ${endpoint}`,
    protocol,
    endpoint,
    platformAccount: stringValue(rows[0]?.platform_name, 'RAW endpoint'),
    health: sourceHealthFromLastFrame(lastFrameAt),
    vehicleCount: Math.max(uniqueVehicles.size, rows.length),
    onlineCount: uniqueVehicles.size,
    latencyP95Ms: percentile(rows.map(rawFrameLatencyMs), 0.95),
    lastFrameAt: formatChinaTime(lastFrameAt),
    owner: realtimeProtocolCount > 0 ? `实时协议覆盖 ${realtimeProtocolCount} 辆` : '仅 RAW 端点样本'
  };
}

function sourceMonitorRecordFromRealtime(protocol: Protocol, rows: RawRealtimeLocation[]): DataSourceRecord {
  const lastFrameAt = rows
    .map((row) => stringValue(row.received_at, row.updated_at, row.event_time))
    .sort((a, b) => parseChinaTime(b) - parseChinaTime(a))[0] ?? '';
  const uniqueVehicles = new Set(rows.map((row) => stringValue(row.vin)).filter(Boolean));
  return {
    id: `real-realtime-${protocol}`,
    name: `${protocolShortLabels[protocol]} 实时协议覆盖`,
    protocol,
    endpoint: 'realtime projection lacks source_endpoint',
    platformAccount: 'Realtime location',
    health: sourceHealthFromLastFrame(lastFrameAt),
    vehicleCount: uniqueVehicles.size,
    onlineCount: rows.filter((row) => statusFromLastSeen(stringValue(row.received_at, row.updated_at, row.event_time)) === 'online').length,
    latencyP95Ms: percentile(rows.map(latencyMs), 0.95),
    lastFrameAt: formatChinaTime(lastFrameAt),
    owner: '实时位置接口未返回发送端'
  };
}

function aggregateSourceMonitor(realtimeRows: RawRealtimeLocation[], rawRows: RawFrameApiRow[]) {
  const realtimeByProtocol = new Map<Protocol, RawRealtimeLocation[]>();
  realtimeRows.forEach((row) => {
    const protocol = asProtocol(row.protocol);
    if (!protocol) {
      return;
    }
    const rows = realtimeByProtocol.get(protocol) ?? [];
    rows.push(row);
    realtimeByProtocol.set(protocol, rows);
  });

  const rawByEndpoint = new Map<string, RawFrameApiRow[]>();
  const rawByProtocol = new Map<Protocol, RawFrameApiRow[]>();
  const endpointsByProtocol = new Map<Protocol, Set<string>>();
  const missingEndpointByProtocol = new Map<Protocol, number>();
  let missingEndpointCount = 0;
  rawRows.forEach((row) => {
    const protocol = asProtocol(row.protocol);
    if (!protocol) {
      return;
    }
    const protocolRows = rawByProtocol.get(protocol) ?? [];
    protocolRows.push(row);
    rawByProtocol.set(protocol, protocolRows);
    const endpoint = stringValue(row.source_endpoint, row.endpoint, row.topic);
    if (!endpoint) {
      missingEndpointCount += 1;
      missingEndpointByProtocol.set(protocol, (missingEndpointByProtocol.get(protocol) ?? 0) + 1);
      return;
    }
    const endpointSet = endpointsByProtocol.get(protocol) ?? new Set<string>();
    endpointSet.add(endpoint);
    endpointsByProtocol.set(protocol, endpointSet);
    const key = `${protocol}|${endpoint}`;
    const rows = rawByEndpoint.get(key) ?? [];
    rows.push(row);
    rawByEndpoint.set(key, rows);
  });

  const endpointRecords = Array.from(rawByEndpoint.entries()).map(([key, rows]) => {
    const [protocol, endpoint] = key.split('|');
    return sourceMonitorRecordFromRaw(
      protocol as Protocol,
      endpoint,
      rows,
      realtimeByProtocol.get(protocol as Protocol)?.length ?? 0
    );
  });
  const records = endpointRecords.length > 0
    ? endpointRecords
    : Array.from(realtimeByProtocol.entries()).map(([protocol, rows]) => sourceMonitorRecordFromRealtime(protocol, rows));
  const protocolEvidence = protocolOrder.map((protocol) => {
    const protocolRecords = records.filter((record) => record.protocol === protocol);
    const healthyEndpoints = protocolRecords.filter((record) => record.health === 'healthy').length;
    const warningEndpoints = protocolRecords.filter((record) => record.health === 'warning').length;
    const downEndpoints = protocolRecords.filter((record) => record.health === 'down').length;
    const lastFrameAt = protocolRecords
      .map((record) => record.lastFrameAt)
      .sort((a, b) => parseChinaTime(b) - parseChinaTime(a))[0] ?? '';
    return {
      protocol,
      realtimeRows: realtimeByProtocol.get(protocol)?.length ?? 0,
      rawRows: rawByProtocol.get(protocol)?.length ?? 0,
      endpointCount: endpointsByProtocol.get(protocol)?.size ?? 0,
      missingEndpointRows: missingEndpointByProtocol.get(protocol) ?? 0,
      healthyEndpoints,
      warningEndpoints,
      downEndpoints,
      lastFrameAt
    };
  });

  return {
    records: records.sort((a, b) => parseChinaTime(b.lastFrameAt) - parseChinaTime(a.lastFrameAt)),
    protocolEvidence,
    rawEndpointCount: rawByEndpoint.size,
    missingEndpointCount
  };
}

function vehicleLabel(row: RawRealtimeLocation | RawHistoryLocation) {
  return stringValue(row.plate, row.vin, 'unknown');
}

function rowProtocol(row: RawRealtimeLocation | RawHistoryLocation) {
  return asProtocol(row.protocol);
}

function rowEventTime(row: RawRealtimeLocation | RawHistoryLocation) {
  return stringValue('event_time' in row ? row.event_time : row.ts, row.received_at);
}

function alertSignalEvidence(row: RawRealtimeLocation | RawHistoryLocation) {
  return [
    { label: 'VIN', value: stringValue(row.vin, 'unknown') },
    { label: '协议', value: stringValue(row.protocol, '--') },
    { label: '设备时间', value: formatChinaTime(rowEventTime(row)) },
    { label: '接收时间', value: formatChinaTime(stringValue(row.received_at)) }
  ];
}

function alertSignalLocation(row: RawRealtimeLocation | RawHistoryLocation) {
  return {
    longitude: optionalNumber(row.longitude),
    latitude: optionalNumber(row.latitude),
    speedKph: optionalNumber('speedKmh' in row ? row.speedKmh : undefined, 'speed_kmh' in row ? row.speed_kmh : undefined),
    totalMileageKm: optionalNumber(
      'totalMileageKm' in row ? row.totalMileageKm : undefined,
      'total_mileage_km' in row ? row.total_mileage_km : undefined
    )
  };
}

function signalFromAlarmRow(row: RawRealtimeLocation | RawHistoryLocation, index: number): PrototypeAlertSignal | undefined {
  const alarmFlag = numberValue(row.alarm_flag);
  if (alarmFlag <= 0) {
    return undefined;
  }
  return {
    id: `alarm-${stringValue(row.event_id, row.vin, index)}`,
    level: 'P1',
    type: '协议告警位',
    title: `${vehicleLabel(row)} 上报 alarm_flag ${alarmFlag}`,
    detail: '来自实时或历史位置帧的协议告警位，仅作为观测信号，需结合 RAW 和后续告警规则确认。',
    protocol: rowProtocol(row),
    vin: stringValue(row.vin),
    plate: stringValue(row.plate),
    time: formatChinaTime(rowEventTime(row)),
    source: 'vehicle_locations / realtime_locations',
    ...alertSignalLocation(row),
    evidence: [...alertSignalEvidence(row), { label: 'alarm_flag', value: String(alarmFlag) }]
  };
}

function signalFromLatencyRow(row: RawRealtimeLocation, index: number): PrototypeAlertSignal | undefined {
  const latency = realtimeLatencyMs(row);
  if (latency <= 10000) {
    return undefined;
  }
  return {
    id: `latency-${stringValue(row.event_id, row.vin, index)}`,
    level: latency > 60000 ? 'P1' : 'P2',
    type: '链路延迟',
    title: `${vehicleLabel(row)} 上报延迟 ${Math.round(latency / 1000)} 秒`,
    detail: '按 received_at - event_time 计算，只表达链路观测延迟，不自动生成通知。',
    protocol: rowProtocol(row),
    vin: stringValue(row.vin),
    plate: stringValue(row.plate),
    time: formatChinaTime(stringValue(row.received_at, row.event_time)),
    source: 'GET /api/realtime/locations',
    ...alertSignalLocation(row),
    evidence: [...alertSignalEvidence(row), { label: '延迟', value: `${latency} ms` }]
  };
}

function signalFromMissingFieldRow(row: RawRealtimeLocation, index: number): PrototypeAlertSignal | undefined {
  const missing: string[] = [];
  if (optionalNumber(row.longitude) == null || optionalNumber(row.latitude) == null) {
    missing.push('经纬度');
  }
  if (optionalNumber(row.total_mileage_km, row.totalMileageKm) == null) {
    missing.push('总里程');
  }
  if (row.protocol === 'GB32960' && optionalNumber(row.soc_percent, row.socPercent) == null) {
    missing.push('SOC');
  }
  if (missing.length === 0) {
    return undefined;
  }
  return {
    id: `missing-${stringValue(row.event_id, row.vin, index)}`,
    level: 'P2',
    type: '字段缺失',
    title: `${vehicleLabel(row)} 缺少 ${missing.join('、')}`,
    detail: '来自实时投影的字段完整性检查；字段缺失只提示当前帧，不代表车辆长期缺失。',
    protocol: rowProtocol(row),
    vin: stringValue(row.vin),
    plate: stringValue(row.plate),
    time: formatChinaTime(stringValue(row.received_at, row.event_time)),
    source: 'GET /api/realtime/locations',
    ...alertSignalLocation(row),
    evidence: [...alertSignalEvidence(row), { label: '缺失字段', value: missing.join(', ') }]
  };
}

function buildAlertSignals(realtimeRows: RawRealtimeLocation[], historyRows: RawHistoryLocation[]) {
  const signals: PrototypeAlertSignal[] = [
    {
      id: 'boundary-alert-api-unavailable',
      level: 'P1',
      type: '能力缺口',
      title: 'Durable 告警 API 未接入',
      detail: '/api/alert-events、/api/quality/issues、/api/alert-events/notification-plan 当前返回 404，本页只展示观测信号。',
      time: formatChinaTime(new Date().toISOString()),
      source: 'BFF alert-events / quality API',
      evidence: [
        { label: '事件接口', value: '404' },
        { label: '通知接口', value: '404' },
        { label: '替代证据', value: 'realtime locations + vehicle_locations' }
      ]
    }
  ];
  realtimeRows.forEach((row, index) => {
    const alarm = signalFromAlarmRow(row, index);
    const latency = signalFromLatencyRow(row, index);
    const missing = signalFromMissingFieldRow(row, index);
    if (alarm) signals.push(alarm);
    if (latency) signals.push(latency);
    if (missing) signals.push(missing);
  });
  historyRows.forEach((row, index) => {
    const alarm = signalFromAlarmRow(row, index + realtimeRows.length);
    if (alarm) {
      signals.push(alarm);
    }
  });
  const unique = new Map<string, PrototypeAlertSignal>();
  signals.forEach((signal) => {
    const key = `${signal.type}-${signal.vin ?? signal.id}-${signal.title}`;
    if (!unique.has(key)) {
      unique.set(key, signal);
    }
  });
  return Array.from(unique.values()).slice(0, 80);
}

function locationState(row: RawHistoryLocation): TrackPoint['state'] {
  if (numberValue(row.alarm_flag) > 0) {
    return 'alert';
  }
  return numberValue(row.speed_kmh) > 0 ? 'running' : 'stop';
}

function locationNote(row: RawHistoryLocation) {
  const protocol = stringValue(row.protocol, 'UNKNOWN');
  const receivedAt = formatChinaTime(stringValue(row.received_at));
  const direction = optionalNumber(row.direction_deg);
  const altitude = optionalNumber(row.altitude_m);
  const alarmFlag = numberValue(row.alarm_flag);
  return [
    `${protocol} 位置上报`,
    receivedAt !== '--' ? `接收 ${receivedAt}` : '',
    alarmFlag > 0 ? `alarm_flag ${alarmFlag}` : '',
    direction == null ? '' : `方向 ${direction}°`,
    altitude == null ? '' : `海拔 ${altitude}m`
  ].filter(Boolean).join(' / ');
}

function trackPointFromLocation(row: RawHistoryLocation, vehicleId: string, index: number): TrackPoint | undefined {
  const longitude = optionalNumber(row.longitude);
  const latitude = optionalNumber(row.latitude);
  if (longitude == null || latitude == null) {
    return undefined;
  }
  const ts = stringValue(row.ts, row.received_at);
  return {
    id: stringValue(row.event_id, `${row.vin ?? vehicleId}-${index + 1}`),
    vehicleId,
    time: formatClockTime(ts),
    timestampMs: parseChinaTime(ts),
    state: locationState(row),
    address: `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`,
    longitude,
    latitude,
    speedKph: numberValue(row.speed_kmh),
    mileageKm: numberValue(row.total_mileage_km),
    note: locationNote(row)
  };
}

function toTrackPoints(rows: RawHistoryLocation[], vehicleId: string) {
  return rows
    .slice()
    .sort((a, b) => parseChinaTime(stringValue(a.ts, a.received_at)) - parseChinaTime(stringValue(b.ts, b.received_at)))
    .map((row, index) => trackPointFromLocation(row, vehicleId, index))
    .filter((point): point is TrackPoint => Boolean(point));
}

function saveRealtimeSessionCache(state: PrototypeRealDataState) {
  if (state.vehicles.length === 0 || typeof window === 'undefined') {
    return;
  }
  try {
    window.sessionStorage.setItem(realtimeSessionCacheKey, JSON.stringify({
      vehicles: state.vehicles,
      total: state.total,
      fetchedAt: state.fetchedAt,
      source: state.source
    }));
  } catch {
    // Cache is only a resilience aid for the prototype; storage failures should not affect live data.
  }
}

function loadRealtimeSessionCache(source: string, error: string): PrototypeRealDataState | undefined {
  if (typeof window === 'undefined') {
    return undefined;
  }
  try {
    const cachedText = window.sessionStorage.getItem(realtimeSessionCacheKey);
    if (!cachedText) {
      return undefined;
    }
    const cached = JSON.parse(cachedText) as Partial<PrototypeRealDataState>;
    if (!Array.isArray(cached.vehicles) || cached.vehicles.length === 0) {
      return undefined;
    }
    return {
      vehicles: cached.vehicles,
      status: 'stale',
      source: `${source} / session cache`,
      fetchedAt: cached.fetchedAt,
      total: cached.total ?? cached.vehicles.length,
      error
    };
  } catch {
    return undefined;
  }
}

export async function loadPrototypeRealtimeVehicles(limit = 120): Promise<PrototypeRealDataState> {
  if (!shouldUsePrototypeRealData()) {
    return {
      vehicles: [],
      status: 'disabled',
      source: '本地样例',
      error: 'prototype real data disabled'
    };
  }
  const locationPath = `/api/realtime/locations?limit=${limit}&offset=0`;
  const snapshotPath = `/api/realtime/snapshots?limit=${limit}&offset=0`;
  const [locationResult, snapshotResult] = await Promise.allSettled([
    fetchJSON<RealDataPayload<RawRealtimeLocation>>(locationPath, 9000),
    fetchJSON<RealDataPayload<RawRealtimeSnapshot>>(snapshotPath, 9000)
  ]);
  const source = `${apiURL(locationPath)} + ${apiURL(snapshotPath)}`;
  if (locationResult.status === 'rejected' && snapshotResult.status === 'rejected') {
    const message = [
      `locations: ${locationResult.reason instanceof Error ? locationResult.reason.message : String(locationResult.reason)}`,
      `snapshots: ${snapshotResult.reason instanceof Error ? snapshotResult.reason.message : String(snapshotResult.reason)}`
    ].join('；');
    const cached = loadRealtimeSessionCache(source, `live request failed: ${message}`);
    if (cached) {
      return cached;
    }
    return {
      vehicles: [],
      status: 'error',
      source,
      error: message
    };
  }

  const locationRows = locationResult.status === 'fulfilled' ? payloadItems(locationResult.value) : [];
  const snapshotRows = snapshotResult.status === 'fulfilled' ? payloadItems(snapshotResult.value) : [];
  const vehicles = mergeRealtimeSnapshots(toVehicles(locationRows), snapshotRows);
  const errors = [
    locationResult.status === 'rejected' ? `locations: ${locationResult.reason instanceof Error ? locationResult.reason.message : String(locationResult.reason)}` : '',
    snapshotResult.status === 'rejected' ? `snapshots: ${snapshotResult.reason instanceof Error ? snapshotResult.reason.message : String(snapshotResult.reason)}` : ''
  ].filter(Boolean);
  const total = Math.max(
    payloadTotal(locationResult.status === 'fulfilled' ? locationResult.value : {}) ?? 0,
    payloadTotal(snapshotResult.status === 'fulfilled' ? snapshotResult.value : {}) ?? 0,
    vehicles.length
  );
  const nextState: PrototypeRealDataState = {
    vehicles,
    status: vehicles.length > 0 ? 'ready' : 'fallback',
    source,
    fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
    total,
    error: vehicles.length > 0
      ? (errors.length > 0 ? errors.join('；') : undefined)
      : errors.length > 0
        ? errors.join('；')
        : 'real API returned empty items'
  };
  if (vehicles.length > 0) {
    saveRealtimeSessionCache(nextState);
    return nextState;
  }
  return loadRealtimeSessionCache(source, nextState.error ?? 'real API returned empty items') ?? nextState;
}

export async function loadPrototypeTrackPoints(vin: string, vehicleId: string, query: PrototypeTrackQuery = {}): Promise<PrototypeTrackState> {
  if (!shouldUsePrototypeRealData()) {
    return {
      points: [],
      status: 'disabled',
      source: '本地样例',
      total: 0,
      error: 'prototype real data disabled'
    };
  }
  const cleanVin = vin.trim();
  const limit = query.limit ?? 80;
  if (!cleanVin) {
    return {
      points: [],
      status: 'empty',
      source: '/api/history/locations',
      total: 0,
      error: 'missing vin'
    };
  }
  const params = new URLSearchParams({
    vin: cleanVin,
    limit: String(limit),
    offset: '0'
  });
  if (query.dateFrom?.trim()) {
    params.set('dateFrom', query.dateFrom.trim());
  }
  if (query.dateTo?.trim()) {
    params.set('dateTo', query.dateTo.trim());
  }
  const path = `/api/history/locations?${params.toString()}`;
  try {
    const payload = await fetchJSON<RealDataPayload<RawHistoryLocation>>(path, 9000);
    const rows = payloadItems(payload);
    const points = toTrackPoints(rows, vehicleId);
    return {
      points,
      status: points.length > 0 ? 'ready' : 'empty',
      source: apiURL(path),
      fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
      total: payloadTotal(payload) ?? points.length,
      error: points.length > 0 ? undefined : 'real API returned empty items'
    };
  } catch (error) {
    return {
      points: [],
      status: 'error',
      source: apiURL(path),
      total: 0,
      error: error instanceof Error ? error.message : String(error)
    };
  }
}

export async function loadPrototypeSourceMonitor(): Promise<PrototypeSourceMonitorState> {
  if (!shouldUsePrototypeRealData()) {
    return {
      records: [],
      protocolEvidence: [],
      status: 'disabled',
      source: '本地样例',
      realtimeRows: 0,
      rawRows: 0,
      rawEndpointCount: 0,
      missingEndpointCount: 0,
      error: 'prototype real data disabled'
    };
  }
  const realtimePath = '/api/realtime/locations?limit=300&offset=0';
  const rawPath = '/api/history/raw-frames/query';
  const [realtimeResult, rawResult] = await Promise.allSettled([
    fetchJSON<RealDataPayload<RawRealtimeLocation>>(realtimePath, 9000),
    fetchJSON<RealDataPayload<RawFrameApiRow>>(rawPath, 9000, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ limit: 240, offset: 0, includeFields: false })
    })
  ]);
  if (realtimeResult.status === 'rejected' && rawResult.status === 'rejected') {
    return {
      records: [],
      protocolEvidence: [],
      status: 'error',
      source: `${apiURL(realtimePath)} + ${apiURL(rawPath)}`,
      realtimeRows: 0,
      rawRows: 0,
      rawEndpointCount: 0,
      missingEndpointCount: 0,
      error: [
        `realtime: ${realtimeResult.reason instanceof Error ? realtimeResult.reason.message : String(realtimeResult.reason)}`,
        `raw: ${rawResult.reason instanceof Error ? rawResult.reason.message : String(rawResult.reason)}`
      ].join('；')
    };
  }
  const realtimeRows = realtimeResult.status === 'fulfilled' ? payloadItems(realtimeResult.value) : [];
  const rawRows = rawResult.status === 'fulfilled' ? payloadItems(rawResult.value) : [];
  const aggregate = aggregateSourceMonitor(realtimeRows, rawRows);
  const errors = [
    realtimeResult.status === 'rejected' ? `realtime: ${realtimeResult.reason instanceof Error ? realtimeResult.reason.message : String(realtimeResult.reason)}` : '',
    rawResult.status === 'rejected' ? `raw: ${rawResult.reason instanceof Error ? rawResult.reason.message : String(rawResult.reason)}` : ''
  ].filter(Boolean);
  const hasSourceEvidence = aggregate.records.length > 0;
  return {
    records: aggregate.records,
    protocolEvidence: aggregate.protocolEvidence,
    status: errors.length > 0 ? 'partial' : hasSourceEvidence ? 'ready' : 'empty',
    source: `${apiURL(realtimePath)} + ${apiURL(rawPath)}`,
    fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
    realtimeRows: realtimeRows.length,
    rawRows: rawRows.length,
    rawEndpointCount: aggregate.rawEndpointCount,
    missingEndpointCount: aggregate.missingEndpointCount,
    error: errors.length > 0 ? errors.join('；') : hasSourceEvidence ? undefined : 'real API returned empty source evidence'
  };
}

export async function loadPrototypeAlertSignals(): Promise<PrototypeAlertSignalState> {
  if (!shouldUsePrototypeRealData()) {
    return {
      signals: [],
      status: 'disabled',
      source: '本地样例',
      realtimeRows: 0,
      historyRows: 0,
      durableAlertApi: 'unknown',
      error: 'prototype real data disabled'
    };
  }
  const realtimePath = '/api/realtime/locations?limit=300&offset=0';
  const historyPath = '/api/history/locations?limit=300&offset=0';
  const [realtimeResult, historyResult] = await Promise.allSettled([
    fetchJSON<RealDataPayload<RawRealtimeLocation>>(realtimePath, 9000),
    fetchJSON<RealDataPayload<RawHistoryLocation>>(historyPath, 9000)
  ]);
  if (realtimeResult.status === 'rejected' && historyResult.status === 'rejected') {
    return {
      signals: [],
      status: 'error',
      source: `${apiURL(realtimePath)} + ${apiURL(historyPath)}`,
      realtimeRows: 0,
      historyRows: 0,
      durableAlertApi: 'unavailable',
      error: [
        `realtime: ${realtimeResult.reason instanceof Error ? realtimeResult.reason.message : String(realtimeResult.reason)}`,
        `history: ${historyResult.reason instanceof Error ? historyResult.reason.message : String(historyResult.reason)}`
      ].join('；')
    };
  }
  const realtimeRows = realtimeResult.status === 'fulfilled' ? payloadItems(realtimeResult.value) : [];
  const historyRows = historyResult.status === 'fulfilled' ? payloadItems(historyResult.value) : [];
  const signals = buildAlertSignals(realtimeRows, historyRows);
  const errors = [
    realtimeResult.status === 'rejected' ? `realtime: ${realtimeResult.reason instanceof Error ? realtimeResult.reason.message : String(realtimeResult.reason)}` : '',
    historyResult.status === 'rejected' ? `history: ${historyResult.reason instanceof Error ? historyResult.reason.message : String(historyResult.reason)}` : ''
  ].filter(Boolean);
  return {
    signals,
    status: errors.length > 0 ? 'partial' : signals.length > 0 ? 'ready' : 'empty',
    source: `${apiURL(realtimePath)} + ${apiURL(historyPath)}`,
    fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
    realtimeRows: realtimeRows.length,
    historyRows: historyRows.length,
    durableAlertApi: 'unavailable',
    error: errors.length > 0 ? errors.join('；') : signals.length > 0 ? undefined : 'real API returned no observable signals'
  };
}

export async function loadPrototypeRawFrames(query: PrototypeRawFrameQuery): Promise<PrototypeRawFrameState> {
  if (!shouldUsePrototypeRealData()) {
    return {
      records: [],
      status: 'disabled',
      source: '本地样例',
      total: 0,
      limit: query.limit,
      offset: query.offset,
      error: 'prototype real data disabled'
    };
  }
  const path = '/api/history/raw-frames/query';
  const fields = expandRawFrameFields(query.fields ?? []);
  const body = {
    ...(query.vin?.trim() ? { vin: query.vin.trim() } : {}),
    ...(query.protocol ? { protocol: query.protocol } : {}),
    ...(query.dateFrom?.trim() ? { dateFrom: query.dateFrom.trim() } : {}),
    ...(query.dateTo?.trim() ? { dateTo: query.dateTo.trim() } : {}),
    includeFields: true,
    ...(fields.length > 0 ? { fields } : {}),
    limit: query.limit,
    offset: query.offset
  };
  try {
    const payload = await fetchJSON<RealDataPayload<RawFrameApiRow>>(path, 9000, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body)
    });
    const records = payloadItems(payload)
      .map(rawFrameFromRow)
      .filter((row): row is PrototypeRawFrameRecord => Boolean(row));
    const total = payloadTotal(payload) ?? records.length;
    return {
      records,
      status: records.length > 0 ? 'ready' : 'empty',
      source: apiURL(path),
      fetchedAt: new Date().toLocaleTimeString('zh-CN', { hour12: false }),
      total,
      limit: query.limit,
      offset: query.offset,
      error: records.length > 0 ? undefined : 'real API returned empty items'
    };
  } catch (error) {
    return {
      records: [],
      status: 'error',
      source: apiURL(path),
      total: 0,
      limit: query.limit,
      offset: query.offset,
      error: error instanceof Error ? error.message : String(error)
    };
  }
}
