"use client";

/**
 * Section2NetworksUtilities — "3. Сети и точки подключения"
 *
 * Layout:
 *   HeadlineBar (title «Сети» + subtitle с расстояниями)
 *   Utilities table: строка на каждый вид инж. сети × [Иконка / Тип /
 *     Ближайший / В радиусе / Статус]
 *   Warning banner if power_line_охранная_зона_flag === true
 *   Карта (точки подключения НСПД + инж.сети OSM + охранные зоны)
 *   Note (caveat text)
 *
 * #1953 — УНИФИКАЦИЯ ИСТОЧНИКОВ: таблица «Сети» выводится из ТОГО ЖЕ источника,
 * что карта — useUtilityInfrastructure → osm_utility_infrastructure_ekb (а НЕ
 * из data.utilities/osm_noise_sources_ekb). Поэтому число в строке таблицы по
 * каждому виду == число объектов этого вида на карте (один и тот же
 * groupUtilityByKind, один и тот же радиус UTILITY_COVERAGE_RADIUS_M).
 *
 * Точки подключения (НСПД) — ОТДЕЛЬНАЯ сущность (useConnectionPoints), показаны
 * отдельным подписанным слоем/счётчиком, чтобы «5 точек подключения» не путали
 * с «инж.сети OSM».
 *
 * data.utilities используется ТОЛЬКО для флага охранной зоны ЛЭП
 * (power_line_охранная_зона_flag) — отдельный safety-сигнал ЗОУИТ, которого нет
 * в OSM-источнике.
 */

import dynamic from "next/dynamic";
import { AlertTriangle, Info, MapPin, Network } from "lucide-react";
import type { Geometry } from "geojson";
import { HeadlineBar } from "@/components/ui/HeadlineBar";
import { NspdVerifyLink } from "./NspdVerifyLink";
import { useParcelAnalyzeQuery } from "@/lib/site-finder-api";
import type { ParcelAnalyzeResponse } from "@/lib/site-finder-api";
import type { ParcelAnalysis } from "@/types/site-finder";
import {
  useConnectionPoints,
  type ConnectionPointsResponse,
} from "@/hooks/useConnectionPoints";
import {
  useUtilityInfrastructure,
  UTILITY_COVERAGE_RADIUS_M,
  type UtilityInfrastructureResponse,
} from "@/hooks/useUtilityInfrastructure";
import {
  useConnectionCapacity,
  type ConnectionCapacityResponse,
} from "@/hooks/useConnectionCapacity";
import { buildUtilityTableRows, type UtilityTableRow } from "./utility-table";
import { ResourceReservesCard } from "./ResourceReservesCard";
import { StageDetails } from "./StageDetails";

// ── Connection-points map (#1746) ─────────────────────────────────────────────
// Высота тайла карты для drill-in уровня раздела. Легенда + CpLayerControlPanel
// рендерятся flow-потоком ПОД картой внутри SiteMap, поэтому обёртка НЕ задаёт
// height/overflow (иначе клипнёт контролы — см. урок MiniMap.tsx #1218).
const CP_MAP_HEIGHT = 340;

// Leaflet использует window → монтируем только на клиенте (ssr:false), как в
// MiniMap.tsx. НЕ импортировать SiteMap в server component.
const SiteMap = dynamic(
  () =>
    import("@/components/site-finder/SiteMap").then((m) => ({
      default: m.SiteMap,
    })),
  {
    ssr: false,
    loading: () => (
      <div
        style={{
          height: CP_MAP_HEIGHT,
          background: "var(--bg-card-alt)",
          borderRadius: 12,
          border: "1px solid var(--border-card)",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          color: "var(--fg-tertiary)",
          fontSize: 13,
        }}
      >
        Загрузка карты...
      </div>
    ),
  },
);

// ── Радиус «зоны охвата» (км) для подписей — тот же, что у карты/хука ──────────

const COVERAGE_RADIUS_KM = UTILITY_COVERAGE_RADIUS_M / 1000;

// ── Distance → presence status ────────────────────────────────────────────────
// Все строки таблицы — объекты В радиусе охвата (UTILITY_COVERAGE_RADIUS_M),
// поэтому отдельного «absent» статуса нет: статус отражает только близость
// ближайшего объекта вида.

type PresenceStatus = "close" | "reachable" | "far";

function getPresenceStatus(nearestM: number): PresenceStatus {
  if (nearestM <= 200) return "close";
  if (nearestM <= 500) return "reachable";
  return "far";
}

const PRESENCE_LABEL: Record<PresenceStatus, string> = {
  close: "≤ 200 м",
  reachable: "≤ 500 м",
  far: `до ${COVERAGE_RADIUS_KM} км`,
};

const PRESENCE_COLOR: Record<PresenceStatus, string> = {
  close: "var(--success)",
  reachable: "var(--accent)",
  far: "var(--warn)",
};

const PRESENCE_BG: Record<PresenceStatus, string> = {
  close: "var(--success-soft)",
  reachable: "var(--accent-soft)",
  far: "var(--warn-soft)",
};

// ── HeadlineBar subtitle helper ───────────────────────────────────────────────

/**
 * Подзаголовок плашки «Сети»: ближайшие расстояния по ключевым видам (электро/
 * газ/вода) из ТОГО ЖЕ источника, что таблица и карта (utility-table rows).
 */
function buildSubtitle(rows: UtilityTableRow[]): string {
  const parts: string[] = [];

  const power = rows.find((r) => r.kind === "power");
  const gas = rows.find((r) => r.kind === "gas");
  const water = rows.find((r) => r.kind === "water");

  if (power?.nearestM != null)
    parts.push(
      `электричество ${Math.round(power.nearestM).toLocaleString("ru")} м`,
    );
  if (gas?.nearestM != null)
    parts.push(`газ ${Math.round(gas.nearestM).toLocaleString("ru")} м`);
  if (water?.nearestM != null)
    parts.push(
      `водопровод ${Math.round(water.nearestM).toLocaleString("ru")} м`,
    );

  return parts.length > 0
    ? parts.join(" · ")
    : `инженерные сети OSM в радиусе ${COVERAGE_RADIUS_KM} км`;
}

// ── Loading skeleton ──────────────────────────────────────────────────────────

function Section2Skeleton() {
  return (
    <section
      id="section-2"
      style={{
        background: "var(--bg-card)",
        border: "1px solid var(--border-card)",
        borderRadius: 12,
        padding: 20,
        marginBottom: 24,
      }}
    >
      <div
        style={{
          height: 56,
          background: "var(--bg-card-alt)",
          borderRadius: 12,
          marginBottom: 16,
        }}
      />
      <div
        style={{
          height: 180,
          background: "var(--bg-card-alt)",
          borderRadius: 10,
        }}
      />
    </section>
  );
}

// ── No-data state ─────────────────────────────────────────────────────────────

function Section2NoData() {
  return (
    <section
      id="section-2"
      style={{
        background: "var(--bg-card)",
        border: "1px solid var(--border-card)",
        borderRadius: 12,
        padding: 20,
        marginBottom: 24,
      }}
    >
      <h2
        style={{
          margin: "0 0 12px",
          fontSize: 18,
          fontWeight: 600,
          color: "var(--fg-primary)",
        }}
      >
        2. Сети и точки подключения
      </h2>
      <div
        style={{
          padding: "20px 16px",
          background: "var(--bg-card-alt)",
          border: "1px dashed var(--border-strong)",
          borderRadius: 10,
          display: "flex",
          alignItems: "center",
          gap: 8,
          color: "var(--fg-tertiary)",
          fontSize: 13,
        }}
      >
        <Info size={16} strokeWidth={1.5} style={{ flexShrink: 0 }} />
        Инженерные сети (OSM) и точки подключения (НСПД) не найдены в радиусе
        охвата для этого участка. Проверьте наличие данных по кадастровому
        кварталу в базе.
      </div>
    </section>
  );
}

// ── Error state ───────────────────────────────────────────────────────────────

function Section2Error({ message }: { message: string }) {
  return (
    <section
      id="section-2"
      style={{
        background: "var(--bg-card)",
        border: "1px solid var(--border-card)",
        borderRadius: 12,
        padding: 20,
        marginBottom: 24,
      }}
    >
      <h2
        style={{
          margin: "0 0 8px",
          fontSize: 18,
          fontWeight: 600,
          color: "var(--fg-primary)",
        }}
      >
        2. Сети и точки подключения
      </h2>
      <p
        style={{ margin: 0, fontSize: 13, color: "var(--danger)" }}
        role="alert"
      >
        {message}
      </p>
    </section>
  );
}

// ── Utilities table ───────────────────────────────────────────────────────────
//
// #1953 — строки выводятся из buildUtilityTableRows(utilityInfrastructure.features),
// т.е. из ТОГО ЖЕ источника и того же groupUtilityByKind, что рисует карта. Поэтому
// столбец «В радиусе» по каждому виду == число объектов этого вида на карте.

const GRID_COLS = "32px 1fr 120px 130px 130px";

interface UtilitiesTableProps {
  rows: UtilityTableRow[];
}

function UtilitiesTable({ rows }: UtilitiesTableProps) {
  return (
    <div
      style={{
        border: "1px solid var(--border-card)",
        borderRadius: 10,
        overflow: "hidden",
      }}
    >
      {/* Table header */}
      <div
        style={{
          display: "grid",
          gridTemplateColumns: GRID_COLS,
          gap: 0,
          background: "var(--bg-card-alt)",
          borderBottom: "1px solid var(--border-soft)",
          padding: "8px 16px",
          alignItems: "center",
        }}
      >
        <div />
        <div
          style={{
            fontSize: 11,
            fontWeight: 600,
            color: "var(--fg-secondary)",
            textTransform: "uppercase",
            letterSpacing: "0.04em",
          }}
        >
          Вид сети
        </div>
        <div
          style={{
            fontSize: 11,
            fontWeight: 600,
            color: "var(--fg-secondary)",
            textTransform: "uppercase",
            letterSpacing: "0.04em",
            textAlign: "right",
          }}
        >
          Ближайший
        </div>
        <div
          style={{
            fontSize: 11,
            fontWeight: 600,
            color: "var(--fg-secondary)",
            textTransform: "uppercase",
            letterSpacing: "0.04em",
            textAlign: "right",
          }}
        >
          В {COVERAGE_RADIUS_KM} км
        </div>
        <div
          style={{
            fontSize: 11,
            fontWeight: 600,
            color: "var(--fg-secondary)",
            textTransform: "uppercase",
            letterSpacing: "0.04em",
            textAlign: "right",
          }}
        >
          Статус
        </div>
      </div>

      {/* Table rows */}
      {rows.map((row, idx) => {
        const status =
          row.nearestM != null ? getPresenceStatus(row.nearestM) : null;
        const isLast = idx === rows.length - 1;

        return (
          <div
            key={row.kind}
            style={{
              display: "grid",
              gridTemplateColumns: GRID_COLS,
              gap: 0,
              padding: "10px 16px",
              alignItems: "center",
              borderBottom: isLast ? "none" : "1px solid var(--border-soft)",
              background: "var(--bg-card)",
            }}
          >
            {/* Color dot — совпадает с цветом вида на карте/в легенде */}
            <div
              style={{
                display: "flex",
                alignItems: "center",
                justifyContent: "flex-start",
              }}
            >
              <span
                aria-hidden
                style={{
                  display: "inline-block",
                  width: 10,
                  height: 10,
                  borderRadius: "50%",
                  background: row.color,
                  flexShrink: 0,
                }}
              />
            </div>

            {/* Label */}
            <div
              style={{
                fontSize: 14,
                color: "var(--fg-primary)",
                fontWeight: 400,
              }}
            >
              {row.label}
            </div>

            {/* Nearest distance */}
            <div
              style={{
                fontSize: 14,
                fontVariantNumeric: "tabular-nums",
                color: "var(--fg-primary)",
                textAlign: "right",
              }}
            >
              {row.nearestM != null
                ? `${Math.round(row.nearestM).toLocaleString("ru")} м`
                : "—"}
            </div>

            {/* Count within coverage radius — == счётчик карты по этому виду */}
            <div
              style={{
                fontSize: 14,
                fontVariantNumeric: "tabular-nums",
                color: "var(--fg-secondary)",
                textAlign: "right",
              }}
            >
              {row.count.toLocaleString("ru")} шт.
            </div>

            {/* Status badge */}
            <div style={{ textAlign: "right" }}>
              {status && (
                <span
                  style={{
                    display: "inline-block",
                    padding: "2px 8px",
                    borderRadius: 6,
                    fontSize: 12,
                    fontWeight: 500,
                    color: PRESENCE_COLOR[status],
                    background: PRESENCE_BG[status],
                  }}
                >
                  {PRESENCE_LABEL[status]}
                </span>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ── Counters legend (явное разделение источников счётчиков) ────────────────────
//
// #1953 — чтобы «5 точек подключения» не путали с «инж.сети OSM», над картой
// показываем два чётко подписанных счётчика с указанием источника и радиуса.

interface SourceCountersProps {
  utilityCount: number;
  connectionPointCount: number;
}

function SourceCounters({
  utilityCount,
  connectionPointCount,
}: SourceCountersProps) {
  return (
    <div
      style={{
        display: "flex",
        flexWrap: "wrap",
        gap: 8,
        marginBottom: 12,
      }}
    >
      {/* Инж. сети (OSM) — тот же счётчик, что в таблице и на карте */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          padding: "8px 12px",
          background: "var(--bg-card-alt)",
          border: "1px solid var(--border-card)",
          borderRadius: 8,
          fontSize: 12,
          color: "var(--fg-secondary)",
        }}
      >
        <Network
          size={16}
          strokeWidth={1.5}
          style={{ color: "var(--viz-4)", flexShrink: 0 }}
        />
        <span>
          Инж. сети (OSM):{" "}
          <strong style={{ color: "var(--fg-primary)" }}>
            {utilityCount.toLocaleString("ru")}
          </strong>{" "}
          объ. в радиусе {COVERAGE_RADIUS_KM} км
        </span>
      </div>

      {/* Точки подключения (НСПД) — отдельная сущность, отдельный счётчик */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          padding: "8px 12px",
          background: "var(--bg-card-alt)",
          border: "1px solid var(--border-card)",
          borderRadius: 8,
          fontSize: 12,
          color: "var(--fg-secondary)",
        }}
      >
        <MapPin
          size={16}
          strokeWidth={1.5}
          style={{ color: "var(--accent)", flexShrink: 0 }}
        />
        <span>
          Точки подключения (НСПД):{" "}
          <strong style={{ color: "var(--fg-primary)" }}>
            {connectionPointCount.toLocaleString("ru")}
          </strong>{" "}
          по кадастровому кварталу
        </span>
      </div>
    </div>
  );
}

// ── Connection-points map block (#1746) ───────────────────────────────────────

/**
 * Адаптирует /analyze-ответ в минимальный ParcelAnalysis, который SiteMap
 * требует обязательным prop `data`. Для контекста карты точек подключения нужен
 * только геом участка (geom_geojson) + cad_num + source — остальные required-
 * поля заполняем безопасными дефолтами (без рыночных слоёв: тут показываем
 * только точки подключения + охранные зоны сетей). Зеркалит toSiteMapData из
 * MiniMap.tsx, но без проброса competitors/pipeline/opportunity (не нужны).
 */
function toCpMapData(data: ParcelAnalyzeResponse): ParcelAnalysis {
  return {
    cad_num: data.cad_num,
    source: data.source === "cad_building" ? "cad_building" : "cad_quarter",
    geom_geojson: (data.geom_geojson as Geometry) ?? null,
    score: data.score,
    score_breakdown: {},
    poi_count: 0,
    competitors: [],
    noise: null,
    air_quality: null,
    wind: null,
    district: data.district ?? null,
    // #255 — ЗОУИТ overlaps (охранные зоны сетей) для слоя ZouitLayer.
    nspd_zouit_overlaps: data.nspd_zouit_overlaps ?? undefined,
  };
}

interface ConnectionPointsMapProps {
  data: ParcelAnalyzeResponse;
  connectionPoints: ConnectionPointsResponse | undefined;
  utilityInfrastructure: UtilityInfrastructureResponse | undefined;
  connectionCapacity: ConnectionCapacityResponse | undefined;
  isLoading: boolean;
}

function ConnectionPointsMap({
  data,
  connectionPoints,
  utilityInfrastructure,
  connectionCapacity,
  isLoading,
}: ConnectionPointsMapProps) {
  const hasPoints =
    !!connectionPoints &&
    connectionPoints.dump_available &&
    connectionPoints.engineering_structures.length > 0;
  const hasUtility =
    !!utilityInfrastructure && utilityInfrastructure.features.length > 0;
  const powerPoints = connectionCapacity?.power_points ?? [];
  const hasPowerPoints = powerPoints.length > 0;
  // Точки выхода газосети (#2119 B2 PR-4) — геокодированы только ~19% строк,
  // список часто пуст (это норма).
  const gasOutletPoints = connectionCapacity?.gas_outlet_points ?? [];
  const hasGasOutlets = gasOutletPoints.length > 0;
  const hasAnyLayer =
    hasPoints || hasUtility || hasPowerPoints || hasGasOutlets;

  return (
    <div style={{ marginTop: 16 }}>
      {/* Label над картой */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 8,
          marginBottom: 4,
        }}
      >
        <MapPin
          size={16}
          strokeWidth={1.5}
          style={{ color: "var(--accent)", flexShrink: 0 }}
        />
        <h3
          style={{
            margin: 0,
            fontSize: 14,
            fontWeight: 600,
            color: "var(--fg-primary)",
          }}
        >
          Точки подключения, охранные зоны и инженерные сети
        </h3>
      </div>
      <p
        style={{
          margin: "0 0 10px",
          fontSize: 12,
          color: "var(--fg-tertiary)",
        }}
      >
        Те же инженерные сети, что в таблице выше, показаны на карте: трассы и
        объекты из OpenStreetMap (ЛЭП, подстанции, трубопроводы) в радиусе{" "}
        {COVERAGE_RADIUS_KM} км — как столбец «В {COVERAGE_RADIUS_KM} км» в
        таблице. Отдельным слоем — точки подключения и охранные зоны из снимка
        кадастрового квартала (НСПД): это разные источники, поэтому их счётчики
        не совпадают. Карта показывает, где находятся эти точки и трассы, а не
        только расстояние до них. Слои переключаются под картой.
      </p>

      {isLoading ? (
        <div
          style={{
            height: CP_MAP_HEIGHT,
            background: "var(--bg-card-alt)",
            borderRadius: 12,
            border: "1px solid var(--border-card)",
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            color: "var(--fg-tertiary)",
            fontSize: 13,
          }}
        >
          Загрузка инженерных сетей...
        </div>
      ) : hasAnyLayer ? (
        <SiteMap
          data={toCpMapData(data)}
          connectionPoints={hasPoints ? connectionPoints : undefined}
          utilityInfrastructure={hasUtility ? utilityInfrastructure : undefined}
          powerConnectionPoints={hasPowerPoints ? powerPoints : undefined}
          gasOutletPoints={hasGasOutlets ? gasOutletPoints : undefined}
          mapHeight={CP_MAP_HEIGHT}
        />
      ) : (
        <div
          style={{
            display: "flex",
            alignItems: "center",
            gap: 8,
            padding: "20px 16px",
            background: "var(--bg-card-alt)",
            border: "1px dashed var(--border-strong)",
            borderRadius: 10,
            color: "var(--fg-tertiary)",
            fontSize: 13,
          }}
        >
          <Info size={16} strokeWidth={1.5} style={{ flexShrink: 0 }} />
          Точки подключения НСПД и данные OSM по инж. сетям не получены для
          этого кадастрового квартала.
        </div>
      )}
    </div>
  );
}

// ── Main component ────────────────────────────────────────────────────────────

interface Props {
  cad: string;
}

export function Section2NetworksUtilities({ cad }: Props) {
  const { isLoading, isError, error, data } = useParcelAnalyzeQuery(cad);

  // #1953 — ЕДИНЫЙ ИСТОЧНИК для таблицы И карты. Оба хука keyed по cad → TanStack
  // дедуплицирует запрос, а таблица/карта/счётчики строятся из ОДНОГО результата,
  // поэтому числа сходятся by construction.
  const { data: utilityInfrastructure, isLoading: utilLoading } =
    useUtilityInfrastructure(data?.cad_num);
  const { data: connectionPoints, isLoading: cpLoading } = useConnectionPoints(
    data?.cad_num,
  );
  // Ресурсные резервы для ТП (#connection-capacity): ЦП Россетей + Водоканал.
  // Keyed по cad → TanStack дедуплицирует; данные идут и в карточку, и в слой карты.
  const { data: connectionCapacity } = useConnectionCapacity(data?.cad_num);

  if (isLoading) return <Section2Skeleton />;

  if (isError || !data) {
    return (
      <Section2Error
        message={
          error instanceof Error
            ? error.message
            : "Не удалось загрузить данные участка"
        }
      />
    );
  }

  const networksLoading = utilLoading || cpLoading;

  // Таблица «Сети» — из ТОГО ЖЕ источника, что карта (osm_utility_infrastructure).
  const tableRows = utilityInfrastructure
    ? buildUtilityTableRows(utilityInfrastructure.features)
    : [];
  const utilityMappableCount = tableRows.reduce((sum, r) => sum + r.count, 0);

  // Точки подключения (НСПД) — ОТДЕЛЬНАЯ сущность, отдельный счётчик. Считаем
  // только при наличии снимка (dump_available), иначе 0.
  const connectionPointCount = connectionPoints?.dump_available
    ? connectionPoints.engineering_structures.length
    : 0;

  // Охранная зона ЛЭП — отдельный safety-флаг из analyze (data.utilities), его нет
  // в OSM-источнике; держим как есть (#1953 не трогает этот сигнал).
  const powerLineZone = data.utilities?.power_line_охранная_зона_flag ?? false;
  const note = data.utilities?.note ?? null;

  // Нет ни одной инж. сети (OSM) и ни одной точки подключения (НСПД), и загрузка
  // завершена → graceful empty-state.
  if (
    !networksLoading &&
    utilityMappableCount === 0 &&
    connectionPointCount === 0
  ) {
    return <Section2NoData />;
  }

  // HeadlineBar subtitle: ближайшие расстояния по ключевым видам из таблицы.
  const headlineSubtitle = buildSubtitle(tableRows);

  return (
    <section
      id="section-2"
      style={{
        background: "var(--bg-card)",
        border: "1px solid var(--border-card)",
        borderRadius: 12,
        padding: 20,
        marginBottom: 24,
      }}
    >
      {/* Section title + verify-link на НСПД (#97) */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          gap: 12,
          flexWrap: "wrap",
          margin: "0 0 12px",
        }}
      >
        <h2
          style={{
            margin: 0,
            fontSize: 18,
            fontWeight: 600,
            color: "var(--fg-primary)",
          }}
        >
          2. Сети и точки подключения
        </h2>
        <NspdVerifyLink
          cadNum={cad}
          source="nspd"
          label="Проверить сети на НСПД"
        />
      </div>

      {/* HeadlineBar */}
      <div style={{ marginBottom: 16 }}>
        <HeadlineBar title="Сети" subtitle={headlineSubtitle} />
      </div>

      <StageDetails>
        {/* Power line охранная зона warning */}
        {powerLineZone && (
          <div
            role="alert"
            style={{
              display: "flex",
              alignItems: "flex-start",
              gap: 10,
              padding: "12px 16px",
              background: "var(--danger-soft)",
              border: "1px solid var(--danger)",
              borderRadius: 10,
              marginBottom: 16,
              fontSize: 13,
              color: "var(--danger)",
            }}
          >
            <AlertTriangle
              size={16}
              strokeWidth={1.5}
              style={{ flexShrink: 0, marginTop: 1 }}
            />
            <span>
              Участок находится в охранной зоне ЛЭП ≥35 кВ. Капитальное
              строительство в охранной зоне (ОЗ) запрещено (СП 36.13330).
              Участок в зоне с особыми условиями использования территорий
              (ЗОУИТ), тип 5.
            </span>
          </div>
        )}

        {/* Карточка «Ресурсные резервы» — свободная мощность для ТП из раскрытия
          сетевых организаций (ЦП Россетей + Водоканал). Над таблицей сетей. */}
        {connectionCapacity && (
          <ResourceReservesCard data={connectionCapacity} />
        )}

        {/* Счётчики источников — явно разделяем «инж.сети OSM» и «точки
          подключения НСПД», чтобы числа не путались (#1953). */}
        {!networksLoading && (
          <SourceCounters
            utilityCount={utilityMappableCount}
            connectionPointCount={connectionPointCount}
          />
        )}

        {/* Utilities table — строки из osm_utility_infrastructure (= карта) */}
        {networksLoading ? (
          <div
            style={{
              height: 160,
              background: "var(--bg-card-alt)",
              borderRadius: 10,
              border: "1px solid var(--border-card)",
              display: "flex",
              alignItems: "center",
              justifyContent: "center",
              color: "var(--fg-tertiary)",
              fontSize: 13,
            }}
          >
            Загрузка инженерных сетей...
          </div>
        ) : tableRows.length > 0 ? (
          <UtilitiesTable rows={tableRows} />
        ) : (
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: 8,
              padding: "16px",
              background: "var(--bg-card-alt)",
              border: "1px dashed var(--border-strong)",
              borderRadius: 10,
              color: "var(--fg-tertiary)",
              fontSize: 13,
            }}
          >
            <Info size={16} strokeWidth={1.5} style={{ flexShrink: 0 }} />
            Инженерные сети (OSM) не найдены в радиусе {COVERAGE_RADIUS_KM} км.
            {connectionPointCount > 0
              ? " Точки подключения (НСПД) показаны на карте ниже."
              : ""}
          </div>
        )}

        {/* Карта точек подключения + охранных зон сетей + инж.сети OSM (#1746) */}
        <ConnectionPointsMap
          data={data}
          connectionPoints={connectionPoints}
          utilityInfrastructure={utilityInfrastructure}
          connectionCapacity={connectionCapacity}
          isLoading={networksLoading}
        />

        {/* Caveat note */}
        {note && (
          <div
            style={{
              display: "flex",
              alignItems: "flex-start",
              gap: 8,
              marginTop: 12,
              padding: "10px 12px",
              background: "var(--bg-card-alt)",
              borderRadius: 8,
              fontSize: 12,
              color: "var(--fg-secondary)",
            }}
          >
            <Info
              size={14}
              strokeWidth={1.5}
              style={{
                flexShrink: 0,
                marginTop: 1,
                color: "var(--fg-tertiary)",
              }}
            />
            <span>{note}</span>
          </div>
        )}
      </StageDetails>
    </section>
  );
}
