const SECRET_MATERIAL = /(?:token|secret|password|authorization|access[_-]?token|refresh[_-]?token|api[_-]?key|decrypt|raw[_-]?params?)/i;
const RESTRICTED_IDENTIFIER = /(?:imei|phone|telephone|address)/i;
const DIRECT_IDENTIFIER_VALUE = /^(?:\+?\d[\d ()-]{8,18}|\d{14,16})$/;

export const DEFAULT_MAP_SUBJECT_DETAIL_PROFILE = Object.freeze({
  id: "map.subject-detail.operational.default",
  version: "1.0.0",
  title: "Техническая карточка объекта",
  semanticTypes: ["map.moving_object"],
  defaultTabId: "overview",
  tabs: [
    {
      id: "overview",
      label: "Обзор",
      emptyMessage: "Оперативные данные объекта пока не получены.",
      sections: [
        {
          id: "state",
          label: "Состояние",
          fields: [
            { id: "overview.signal", aspectId: "primary", source: "attribute", field: "signal_state", label: "Связь", format: "signal_state" },
            { id: "overview.movement", aspectId: "primary", source: "attribute", field: "movement_state", label: "Движение", format: "movement_state" },
            { id: "overview.speed", aspectId: "primary", source: "attribute", field: "speed_kph", label: "Скорость", format: "number", unit: "км/ч" },
            { id: "overview.observed", aspectId: "primary", source: "fact", field: "observedAt", label: "Данные объекта", format: "timestamp" },
          ],
        },
      ],
    },
    {
      id: "position",
      label: "Позиция",
      emptyMessage: "Для объекта нет разрешённой позиции.",
      sections: [
        {
          id: "coordinates",
          label: "Координаты и движение",
          fields: [
            { id: "position.latitude", aspectId: "primary", source: "geometry", field: "latitude", label: "Широта", format: "coordinate" },
            { id: "position.longitude", aspectId: "primary", source: "geometry", field: "longitude", label: "Долгота", format: "coordinate" },
            { id: "position.course", aspectId: "primary", source: "attribute", field: "course_degrees", label: "Курс", format: "number", unit: "°" },
            { id: "position.elevation", aspectId: "primary", source: "attribute", field: "elevation_meters", label: "Высота", format: "number", unit: "м" },
          ],
        },
        {
          id: "quality",
          label: "Качество GPS",
          fields: [
            { id: "position.satellites", aspectId: "primary", source: "attribute", field: "satellite_count", label: "Спутники", format: "number" },
            { id: "position.hdop", aspectId: "primary", source: "attribute", field: "hdop", label: "HDOP", format: "number" },
            { id: "position.accuracy", aspectId: "primary", source: "attribute", field: "horizontal_accuracy_meters", label: "Точность", format: "number", unit: "м" },
          ],
        },
      ],
    },
    {
      id: "telemetry",
      label: "Телеметрия",
      emptyMessage: "Разрешённые сенсоры появятся после профилирования подключения.",
      sections: [{
        id: "readings",
        label: "Датчики",
        fields: [{
          id: "telemetry.readings",
          aspectId: "primary",
          source: "attribute",
          field: "sensor_readings",
          label: "Датчики",
          format: "telemetry_readings",
          allowedReadingIds: [],
        }],
      }],
    },
    {
      id: "counters",
      label: "Счётчики",
      emptyMessage: "Счётчики для объекта не получены.",
      sections: [{
        id: "operation",
        label: "Эксплуатация",
        fields: [
          { id: "counters.mileage", aspectId: "primary", source: "attribute", field: "mileage_km", label: "Пробег", format: "number", unit: "км" },
          { id: "counters.engine-hours", aspectId: "primary", source: "attribute", field: "engine_hours", label: "Моточасы", format: "number", unit: "ч" },
        ],
      }],
    },
    { id: "equipment", label: "Оборудование", emptyMessage: "Технический профиль оборудования ещё не подключён.", sections: [] },
    { id: "settings", label: "Настройки", emptyMessage: "Trip/fuel settings ещё не подключены.", sections: [] },
    { id: "maintenance", label: "ТО", emptyMessage: "Планы технического обслуживания ещё не подключены.", sections: [] },
    {
      id: "diagnostics",
      label: "Данные",
      emptyMessage: "Диагностические метаданные не получены.",
      sections: [{
        id: "provenance",
        label: "Происхождение",
        fields: [
          { id: "diagnostics.source-id", aspectId: "primary", source: "fact", field: "sourceId", label: "ID объекта", format: "text" },
          { id: "diagnostics.semantic-type", aspectId: "primary", source: "fact", field: "semanticType", label: "Семантический тип", format: "text" },
          { id: "diagnostics.product", aspectId: "primary", source: "context", field: "dataProductId", label: "Продукт данных", format: "text" },
          { id: "diagnostics.received", aspectId: "primary", source: "fact", field: "receivedAt", label: "Получено NODE.DC", format: "timestamp" },
        ],
      }],
    },
  ],
});

export function buildMapSubjectCardModel(fact, context = {}) {
  if (!isPlainObject(fact)) return null;
  const profile = isPlainObject(context.profile) ? context.profile : DEFAULT_MAP_SUBJECT_DETAIL_PROFILE;
  const primary = {
    fact,
    dataProductId: context.dataProductId,
    bindingId: context.bindingId,
    dataClass: context.dataClass ?? "operational",
  };
  const aspects = { primary, ...(isPlainObject(context.aspects) ? context.aspects : {}) };
  const tabs = Array.isArray(profile.tabs) ? profile.tabs.flatMap((tab) => buildTab(tab, aspects)) : [];
  if (!tabs.length) return null;
  const attributes = isPlainObject(fact.attributes) ? fact.attributes : {};
  const defaultTabId = tabs.some((tab) => tab.id === profile.defaultTabId) ? profile.defaultTabId : tabs[0].id;
  return {
    title: stringValue(attributes.display_name) || stringValue(context.title) || stringValue(fact.sourceId) || "Объект",
    sourceId: stringValue(fact.sourceId),
    profileId: stringValue(profile.id),
    defaultTabId,
    tabs,
  };
}

function buildTab(tab, aspects) {
  if (!isPlainObject(tab) || !safeIdentifier(tab.id) || !stringValue(tab.label)) return [];
  const sections = Array.isArray(tab.sections) ? tab.sections.flatMap((section) => buildSection(section, aspects)) : [];
  return [{
    id: tab.id,
    label: tab.label,
    emptyMessage: stringValue(tab.emptyMessage) || "Данные для этого раздела пока не получены.",
    sections,
    empty: sections.length === 0,
  }];
}

function buildSection(section, aspects) {
  if (!isPlainObject(section) || !safeIdentifier(section.id) || !stringValue(section.label)) return [];
  const rows = [];
  const readings = [];
  for (const field of Array.isArray(section.fields) ? section.fields : []) {
    if (!isPlainObject(field)
      || SECRET_MATERIAL.test(String(field.field || ""))
      || SECRET_MATERIAL.test(String(field.label || ""))) continue;
    const aspect = aspects[stringValue(field.aspectId) || "primary"];
    if (!isPlainObject(aspect) || !isPlainObject(aspect.fact)) continue;
    const restricted = field.dataClass === "restricted" && aspect.dataClass === "restricted";
    if (
      (RESTRICTED_IDENTIFIER.test(String(field.field || ""))
        || RESTRICTED_IDENTIFIER.test(String(field.label || "")))
      && !restricted
    ) continue;
    const value = resolveFieldValue(aspect, field);
    if (field.format === "telemetry_readings") {
      readings.push(...normalizeTelemetryReadings(value, field.allowedReadingIds));
      continue;
    }
    if (field.format === "string_list" && (!Array.isArray(value) || value.length === 0)) continue;
    if (value === undefined || value === null || value === "") continue;
    if (typeof value === "string" && DIRECT_IDENTIFIER_VALUE.test(value.trim()) && !restricted) continue;
    rows.push({
      key: String(field.id || `${field.source}.${field.field}`),
      label: stringValue(field.label),
      value: formatValue(field.format, value, stringValue(field.unit)),
    });
  }
  if (!rows.length && !readings.length) return [];
  return [{ id: section.id, label: section.label, rows, readings }];
}

function resolveFieldValue(aspect, field) {
  const fact = aspect.fact;
  if (field.source === "attribute") return isPlainObject(fact.attributes) ? fact.attributes[field.field] : undefined;
  if (field.source === "fact") return fact[field.field];
  if (field.source === "context") return aspect[field.field];
  if (field.source === "geometry" && fact.geometry?.type === "Point" && Array.isArray(fact.geometry.coordinates)) {
    if (field.field === "latitude") return fact.geometry.coordinates[1];
    if (field.field === "longitude") return fact.geometry.coordinates[0];
  }
  return undefined;
}

export function normalizeTelemetryReadings(value, allowedReadingIds = []) {
  if (!Array.isArray(value) || !Array.isArray(allowedReadingIds) || allowedReadingIds.length === 0) return [];
  const allowed = new Set(allowedReadingIds.filter(
    (item) => safeIdentifier(item) && !SECRET_MATERIAL.test(item) && !RESTRICTED_IDENTIFIER.test(item),
  ));
  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 (!allowed.has(id)
      || !label
      || seen.has(id)
      || SECRET_MATERIAL.test(id)
      || SECRET_MATERIAL.test(label)
      || RESTRICTED_IDENTIFIER.test(id)
      || RESTRICTED_IDENTIFIER.test(label)) return [];
    if (!isSafeScalar(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(format, value, unit) {
  if (format === "timestamp") return isIsoTimestamp(value) ? formatTimestamp(value) : "—";
  if (format === "signal_state") return value === "active" ? "На связи" : value === "inactive" ? "Не на связи" : formatScalar(value, unit);
  if (format === "movement_state") return value === "moving" ? "В движении" : value === "stopped" ? "Неподвижен" : formatScalar(value, unit);
  if (format === "boolean") return Boolean(value) ? "Да" : "Нет";
  if (format === "string_list") {
    if (!Array.isArray(value)) return "—";
    const items = value.slice(0, 256).flatMap((item) => {
      const scalar = formatScalar(item);
      return scalar === "—" ? [] : [scalar.replace(/^([^=]{1,160})=/, "$1: ")];
    });
    return items.length ? items.join(" • ") : "—";
  }
  if (format === "coordinate" && typeof value === "number" && Number.isFinite(value)) {
    return new Intl.NumberFormat("ru-RU", { minimumFractionDigits: 5, maximumFractionDigits: 6 }).format(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 isSafeScalar(value) {
  if (typeof value === "boolean") return true;
  if (typeof value === "number") return Number.isFinite(value) && (!Number.isInteger(value) || Math.abs(value) < 1_000_000_000_000);
  if (typeof value !== "string") return false;
  const normalized = value.trim();
  return normalized.length <= 512
    && !SECRET_MATERIAL.test(normalized)
    && !RESTRICTED_IDENTIFIER.test(normalized)
    && !DIRECT_IDENTIFIER_VALUE.test(normalized);
}

function safeIdentifier(value) {
  return typeof value === "string" && /^[a-z][a-z0-9._:-]{1,159}$/.test(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);
}
