const SECRET_LIKE_KEY = /(token|secret|password|authorization|access[_-]?token|refresh[_-]?token|api[_-]?key|imei|phone|decrypt|address|raw[_-]?params?)/i;

const FIELD_PRESENTATION = Object.freeze({
  signal_state: { section: "state", label: "Связь" },
  movement_state: { section: "state", label: "Движение" },
  speed_kph: { section: "position", label: "Скорость", unit: "км/ч" },
  course_degrees: { section: "position", label: "Курс", unit: "°" },
  elevation_meters: { section: "position", label: "Высота", unit: "м" },
  satellite_count: { section: "quality", label: "Спутники" },
  hdop: { section: "quality", label: "HDOP" },
  horizontal_accuracy_meters: { section: "quality", label: "Точность", unit: "м" },
  mileage_km: { section: "operation", label: "Пробег", unit: "км" },
  engine_hours: { section: "operation", label: "Моточасы", unit: "ч" },
  object_kind: { section: "identity", label: "Класс объекта" },
  position_source: { section: "identity", label: "Источник позиции" },
});

const SECTION_ORDER = Object.freeze([
  ["state", "Состояние"],
  ["position", "Позиция"],
  ["quality", "Качество GPS"],
  ["operation", "Эксплуатация"],
  ["identity", "Идентификация"],
  ["other", "Прочие данные"],
]);

export function buildMapSubjectCardModel(fact, context = {}) {
  if (!fact || typeof fact !== "object") return null;
  const attributes = isPlainObject(fact.attributes) ? fact.attributes : {};
  const rows = new Map(SECTION_ORDER.map(([id, label]) => [id, { id, label, rows: [] }]));
  const append = (section, key, label, value, unit) => {
    if (value === undefined || SECRET_LIKE_KEY.test(key)) return;
    rows.get(section)?.rows.push({ key, label, value: formatValue(key, value, unit) });
  };

  append("identity", "source_id", "ID", fact.sourceId);
  append("identity", "semantic_type", "Семантический тип", fact.semanticType);
  append("identity", "data_product_id", "Продукт данных", context.dataProductId);
  append("identity", "binding_id", "Binding", context.bindingId);
  append("state", "presentation_status", "Статус отображения", fact.presentationStatus);

  if (fact.geometry?.type === "Point" && Array.isArray(fact.geometry.coordinates)) {
    append("position", "latitude", "Широта", fact.geometry.coordinates[1]);
    append("position", "longitude", "Долгота", fact.geometry.coordinates[0]);
  }
  append("state", "observed_at", "Данные объекта", fact.observedAt);
  append("state", "received_at", "Получено NODE.DC", fact.receivedAt);

  for (const [key, value] of Object.entries(attributes)) {
    if (key === "display_name" || key === "sensor_readings" || SECRET_LIKE_KEY.test(key)) continue;
    const presentation = FIELD_PRESENTATION[key];
    append(presentation?.section ?? "other", key, presentation?.label ?? humanizeKey(key), value, presentation?.unit);
  }

  const readings = normalizeTelemetryReadings(attributes.sensor_readings);
  return {
    title: stringValue(attributes.display_name) || stringValue(context.title) || stringValue(fact.sourceId) || "Объект",
    sourceId: stringValue(fact.sourceId),
    sections: [...rows.values()].filter((section) => section.rows.length),
    readings,
  };
}

export function normalizeTelemetryReadings(value) {
  if (!Array.isArray(value)) return [];
  const seen = new Set();
  return value.slice(0, 128).flatMap((candidate) => {
    if (!isPlainObject(candidate)) return [];
    const id = stringValue(candidate.id);
    const label = stringValue(candidate.label);
    if (!/^[a-z][a-z0-9._:-]{1,127}$/.test(id) || !label || seen.has(id) || SECRET_LIKE_KEY.test(id)) return [];
    if (!isScalar(candidate.value)) return [];
    seen.add(id);
    const unit = stringValue(candidate.unit).slice(0, 32);
    const observedAt = isIsoTimestamp(candidate.observedAt) ? candidate.observedAt : undefined;
    return [{ id, label: label.slice(0, 160), value: formatScalar(candidate.value, unit), observedAt }];
  });
}

function formatValue(key, value, unit) {
  if ((key === "observed_at" || key === "received_at") && isIsoTimestamp(value)) return formatTimestamp(value);
  if (key === "signal_state") return value === "active" ? "На связи" : value === "inactive" ? "Не на связи" : formatScalar(value, unit);
  if (key === "movement_state") return value === "moving" ? "В движении" : value === "stopped" ? "Неподвижен" : formatScalar(value, unit);
  if (Array.isArray(value)) return value.map((item) => formatScalar(item)).join(", ");
  if (isPlainObject(value)) return JSON.stringify(value);
  return formatScalar(value, unit);
}

function formatScalar(value, unit = "") {
  if (typeof value === "boolean") return value ? "Да" : "Нет";
  if (typeof value === "number") {
    const number = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 3 }).format(value);
    return unit ? `${number} ${unit}` : number;
  }
  const text = stringValue(value);
  return unit && text ? `${text} ${unit}` : text || "—";
}

function formatTimestamp(value) {
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString("ru-RU");
}

function humanizeKey(value) {
  const text = String(value).replace(/[._-]+/g, " ").trim();
  return text ? text[0].toUpperCase() + text.slice(1) : String(value);
}

function isScalar(value) {
  return typeof value === "string"
    || typeof value === "boolean"
    || (typeof value === "number" && Number.isFinite(value));
}

function stringValue(value) {
  return typeof value === "string" ? value.trim() : "";
}

function isIsoTimestamp(value) {
  return typeof value === "string" && !Number.isNaN(Date.parse(value));
}

function isPlainObject(value) {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
