export const FLEET_POSITIONS_CURRENT = Object.freeze({
  id: "fleet.positions.current.v1",
  version: "1.0.0",
  delivery: "snapshot+patch",
  semanticTypes: Object.freeze(["map.moving_object", "geo.position"]),
});

export function toFleetPositionsSnapshot(rows, { tenantId, connectionId, generatedAt = new Date(), staleAfterMs }) {
  return {
    schemaVersion: "nodedc.data-product.snapshot.v1",
    dataProduct: FLEET_POSITIONS_CURRENT,
    scope: { tenantId, connectionId },
    generatedAt: iso(generatedAt),
    entities: rows.map((row) => toFleetPositionEntity(row, { generatedAt, staleAfterMs })),
  };
}

export function toFleetPositionPatch(row, { generatedAt = new Date(), staleAfterMs }) {
  return {
    schemaVersion: "nodedc.data-product.patch.v1",
    dataProductId: FLEET_POSITIONS_CURRENT.id,
    operation: "upsert",
    emittedAt: iso(generatedAt),
    entity: toFleetPositionEntity(row, { generatedAt, staleAfterMs }),
  };
}

export function toFleetPositionEntity(row, { generatedAt, staleAfterMs }) {
  const observedAt = date(row.observedAt);
  const receivedAt = date(row.receivedAt);
  const latitude = finiteNumber(row.latitude);
  const longitude = finiteNumber(row.longitude);
  const hasPosition = latitude !== null && longitude !== null;
  const ageMs = observedAt ? Math.max(0, generatedAt.getTime() - observedAt.getTime()) : null;
  const status = !hasPosition ? "no-position" : ageMs !== null && ageMs > staleAfterMs ? "stale" : "active";

  return {
    subjectId: String(row.subjectId),
    semanticType: "map.moving_object",
    label: String(row.name || row.subjectId),
    status,
    observedAt: observedAt ? observedAt.toISOString() : null,
    receivedAt: receivedAt ? receivedAt.toISOString() : null,
    position: hasPosition ? { latitude, longitude } : null,
    motion: {
      speed: finiteNumber(row.speed),
      course: finiteNumber(row.course),
    },
    quality: {
      gpsValid: booleanOrNull(row.gpsValid),
      satellites: finiteNumber(row.satellites),
      hdop: finiteNumber(row.hdop),
      accuracyM: finiteNumber(row.accuracyM),
    },
  };
}

function iso(value) {
  const parsed = date(value);
  if (!parsed) throw new Error("data_product_timestamp_invalid");
  return parsed.toISOString();
}

function date(value) {
  if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
  const parsed = new Date(value);
  return Number.isNaN(parsed.getTime()) ? null : parsed;
}

function finiteNumber(value) {
  if (value === null || value === undefined || value === "") return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function booleanOrNull(value) {
  if (value === true || value === 1 || value === "1" || value === "true") return true;
  if (value === false || value === 0 || value === "0" || value === "false") return false;
  return null;
}
