/**
 * connection-capacity — общие хелперы для §3 «Сети»: ресурсные резервы (ЦП
 * Россетей + Водоканал). Используются и карточкой «Ресурсные резервы»
 * (Section2NetworksUtilities), и слоем центров питания на карте
 * (PowerConnectionPointsLayer) — чтобы цвета/подписи/приведение чисел были
 * идентичны в обоих местах.
 */

import type {
  GasOutletPoint,
  NearbyNetworkZone,
  PowerConnectionPoint,
  WaterCapacityRow,
} from "@/hooks/useConnectionCapacity";

// ── Coercion (Pydantic Decimal → строка) ──────────────────────────────────────
//
// NUMERIC-поля БД сериализуются Pydantic v2 как СТРОКИ («12.5»), хотя OpenAPI-тип
// объявлен number | null. `typeof === "number"`-гварды молча роняют такое поле
// (см. урок MarketLayers.toFiniteNumber). Приводим руками. Локальная копия по
// образцу MarketLayers — намеренно, чтобы не тянуть map-модуль в неграфические
// места.
export function toFiniteNumber(
  v: number | string | null | undefined,
): number | null {
  if (v === null || v === undefined) return null;
  const n = typeof v === "string" ? Number(v) : v;
  return Number.isFinite(n) ? n : null;
}

// ── Индекс загрузки центра питания (load_index) ────────────────────────────────

export type LoadIndex = "open" | "limited" | "closed";

export function classifyLoadIndex(
  raw: string | null | undefined,
): LoadIndex | null {
  switch (raw) {
    case "open":
    case "limited":
    case "closed":
      return raw;
    default:
      return null;
  }
}

// Бейдж статуса ТП по индексу загрузки. Цвета — semantic-палитра карточек §3
// (совпадает с SEVERITY_COLOR в Pipeline24moBlock; для карты — документированное
// token-hex исключение popup'ов).
export const LOAD_INDEX_BADGE: Record<
  LoadIndex,
  { label: string; bg: string; fg: string; border: string }
> = {
  open: {
    label: "открыт для ТП",
    bg: "#dcfce7",
    fg: "#15803d",
    border: "#86efac",
  },
  limited: {
    label: "ограничен",
    bg: "#fef9c3",
    fg: "#a16207",
    border: "#fde68a",
  },
  closed: {
    label: "закрыт",
    bg: "#fee2e2",
    fg: "#b91c1c",
    border: "#fca5a5",
  },
};

// Цвет маркера ЦП на карте по индексу загрузки (null → нейтральный серый).
export const LOAD_INDEX_MARKER_COLOR: Record<LoadIndex, string> = {
  open: "#15803d",
  limited: "#a16207",
  closed: "#b91c1c",
};

export const LOAD_INDEX_NULL_COLOR = "#6b7280";

export function loadIndexColor(raw: string | null | undefined): string {
  const idx = classifyLoadIndex(raw);
  return idx ? LOAD_INDEX_MARKER_COLOR[idx] : LOAD_INDEX_NULL_COLOR;
}

export function humanizeLoadIndex(
  raw: string | null | undefined,
): string | null {
  const idx = classifyLoadIndex(raw);
  return idx ? LOAD_INDEX_BADGE[idx].label : null;
}

// ── Форматирование чисел ───────────────────────────────────────────────────────

// МВА с одним знаком после запятой (резерв/мощность ЦП). Приводит строку→число.
// unit — необязательный override единицы измерения (бэк отдаёт reserve_unit;
// напр. «кВт» вместо «МВА» для отдельных ЦП). null/undefined → дефолт «МВА».
export function formatMva(
  v: number | string | null | undefined,
  unit?: string | null,
): string | null {
  const n = toFiniteNumber(v);
  if (n === null) return null;
  const u = unit?.trim() || "МВА";
  return `${n.toLocaleString("ru-RU", {
    minimumFractionDigits: 1,
    maximumFractionDigits: 1,
  })} ${u}`;
}

// тыс. м³/сут (резерв воды/канализации). Может быть ОТРИЦАТЕЛЬНЫМ (дефицит) —
// знак сохраняем (Unicode-минус даёт toLocaleString).
export function formatThousandM3Day(
  v: number | string | null | undefined,
): string | null {
  const n = toFiniteNumber(v);
  if (n === null) return null;
  return `${n.toLocaleString("ru-RU", {
    minimumFractionDigits: 1,
    maximumFractionDigits: 1,
  })} тыс. м³/сут`;
}

export function formatMeters(
  v: number | string | null | undefined,
): string | null {
  const n = toFiniteNumber(v);
  if (n === null) return null;
  return `${Math.round(n).toLocaleString("ru-RU")} м`;
}

// тыс. м³/ч (свободная/проектная мощность ГРС). Приводит строку→число (Decimal→str).
//
// Честность околонуля: при 1 знаке «0,044» округлился бы в «0,0» и ГРС с
// НЕнулевой (но крохотной) свободной мощностью выглядела бы закрытой. Для
// 0 < |v| < 0,05 показываем 2 знака («0,04» / «−0,04»); настоящий 0 — «0,0».
export function formatThousandM3Hour(
  v: number | string | null | undefined,
): string | null {
  const n = toFiniteNumber(v);
  if (n === null) return null;
  const digits = n !== 0 && Math.abs(n) < 0.05 ? 2 : 1;
  return `${n.toLocaleString("ru-RU", {
    minimumFractionDigits: digits,
    maximumFractionDigits: digits,
  })} тыс. м³/ч`;
}

// млн. м³/мес (свободная мощность точки выхода газосети, форма 6 Екатеринбурггаза —
// ВНИМАНИЕ: единица отличается от ГРС, где тыс. м³/ч). Может быть ОТРИЦАТЕЛЬНОЙ
// (дефицит со знаком) — знак сохраняем. Три знака после запятой: источник даёт
// «0.000». Приводит строку→число (Decimal→str).
export function formatMlnM3Month(
  v: number | string | null | undefined,
): string | null {
  const n = toFiniteNumber(v);
  if (n === null) return null;
  return `${n.toLocaleString("ru-RU", {
    minimumFractionDigits: 3,
    maximumFractionDigits: 3,
  })} млн м³/мес`;
}

// Гкал/ч (резерв тепловой мощности системы теплоснабжения). Может быть
// ОТРИЦАТЕЛЬНЫМ (система исчерпана) — знак сохраняем. Приводит строку→число.
export function formatGcalHour(
  v: number | string | null | undefined,
): string | null {
  const n = toFiniteNumber(v);
  if (n === null) return null;
  return `${n.toLocaleString("ru-RU", {
    minimumFractionDigits: 1,
    maximumFractionDigits: 1,
  })} Гкал/ч`;
}

// ── Вода: выбор «городской» системы + группировка ──────────────────────────────
//
// Городская система ЦСВ/ЦСК выделяется в карточке отдельной строкой: сначала по
// имени (содержит «ЗФС» — Западная фильтровальная станция ЕКБ), иначе — самая
// крупная по |резерв| (крупнейшая система города). Остальные сворачиваются.

export function pickCitySystem(
  rows: WaterCapacityRow[],
): WaterCapacityRow | null {
  if (rows.length === 0) return null;
  const byName = rows.find((r) => (r.system_name ?? "").includes("ЗФС"));
  if (byName) return byName;
  // Самая крупная по модулю резерва (city-scale система).
  let best: WaterCapacityRow | null = null;
  let bestAbs = -Infinity;
  for (const r of rows) {
    const n = toFiniteNumber(r.reserve_thousand_m3_day);
    const abs = n === null ? -Infinity : Math.abs(n);
    if (abs > bestAbs) {
      bestAbs = abs;
      best = r;
    }
  }
  return best ?? rows[0];
}

// Множественное число «система» для «+N посёлковых систем».
export function pluralizeSystems(n: number): string {
  const mod10 = n % 10;
  const mod100 = n % 100;
  if (mod10 === 1 && mod100 !== 11) return `${n} система`;
  if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20))
    return `${n} системы`;
  return `${n} систем`;
}

// Ближайший ЦП с резервом → строка-summary для карточки. Порядок нужен, чтобы
// НЕ раскрывать null-поля муторным «нет данных».
export function powerPointReserveIsNegative(p: PowerConnectionPoint): boolean {
  const n = toFiniteNumber(p.reserve_mva);
  return n !== null && n <= 0;
}

// ── Точки выхода газосети (форма 6 Екатеринбурггаза) ───────────────────────────
//
// Свободная мощность точки выхода классифицируется по ЗНАКУ:
//   profit  — free_capacity_mln_m3 > 0 (профицит), зелёный;
//   deficit — < 0 (дефицит со знаком), красный;
//   calc    — needs_calc=true / null (нужен гидравлический расчёт), жёлтый.
// Маркеры карты используют hex токенов --success/--danger/--warn (документированное
// исключение raw-Leaflet попапов, ui-tokens.md), карточка — CSS-переменные напрямую.

export type GasOutletState = "profit" | "deficit" | "calc";

// Hex-зеркала токенов (--success/--danger/--warn) для CircleMarker fillColor.
export const GAS_OUTLET_MARKER_COLOR: Record<GasOutletState, string> = {
  profit: "#0A7A3A", // --success
  deficit: "#B3261E", // --danger
  calc: "#9A6700", // --warn
};

export function classifyGasOutlet(p: GasOutletPoint): GasOutletState {
  if (p.needs_calc) return "calc";
  const n = toFiniteNumber(p.free_capacity_mln_m3);
  if (n === null) return "calc";
  return n < 0 ? "deficit" : "profit";
}

export function gasOutletColor(p: GasOutletPoint): string {
  return GAS_OUTLET_MARKER_COLOR[classifyGasOutlet(p)];
}

// Множественное число «точка» для «Точки выхода газа (N)» / счётчиков.
export function pluralizeOutletPoints(n: number): string {
  const mod10 = n % 10;
  const mod100 = n % 100;
  if (mod10 === 1 && mod100 !== 11) return `${n} точка`;
  if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20))
    return `${n} точки`;
  return `${n} точек`;
}

// ── Сети рядом с участком (охранные зоны cad_zouit, #2119 C2) ───────────────────
//
// nearby_network_zones — где ФИЗИЧЕСКИ проходит сеть (охранный коридор) в радиусе
// 300 м. Тепловая зона с малой дистанцией = дешёвая врезка → главный позитив-сигнал
// §3. net_kind — Literal бэка (сериализуется как string), нормализуем к known-набору.

export type NetKind =
  "thermal" | "electricity" | "gas" | "communication" | "general";

// Русская метка net_kind (маленький нейтральный чип рядом с human_label).
const NET_KIND_LABEL: Record<NetKind, string> = {
  thermal: "тепло",
  electricity: "электро",
  gas: "газ",
  communication: "связь",
  general: "не уточнено",
};

export function classifyNetKind(raw: string | null | undefined): NetKind {
  switch (raw) {
    case "thermal":
    case "electricity":
    case "gas":
    case "communication":
      return raw;
    default:
      // Бэк отдаёт "general" для неклассифицированных; неизвестное → тоже general.
      return "general";
  }
}

export function humanizeNetKind(raw: string | null | undefined): string {
  return NET_KIND_LABEL[classifyNetKind(raw)];
}

// Порог «тепло рядом» — thermal с distance_m ≤ этого выводим первым и подсвечиваем
// позитивным тоном (дешёвая врезка в теплотрассу). Согласован с C2-логикой §3.
export const THERMAL_NEAR_M = 50;

export function isThermalNear(z: NearbyNetworkZone): boolean {
  return (
    classifyNetKind(z.net_kind) === "thermal" && z.distance_m <= THERMAL_NEAR_M
  );
}

// Дистанция зоны → бейдж. distance_m=0 = зона пересекает участок («по участку»).
export function formatZoneDistance(distanceM: number): string {
  if (distanceM <= 0) return "по участку";
  return `в ${Math.round(distanceM).toLocaleString("ru-RU")} м`;
}

// Сортировка для отображения: сначала thermal-near (главный сигнал), затем — исходный
// порядок бэка (уже (distance_m, net_kind)). Stable — сохраняет тай-брейки сервера.
export function sortNearbyNetworkZones(
  zones: readonly NearbyNetworkZone[],
): NearbyNetworkZone[] {
  return zones
    .map((zone, i) => ({ zone, i }))
    .sort((a, b) => {
      const aNear = isThermalNear(a.zone) ? 0 : 1;
      const bNear = isThermalNear(b.zone) ? 0 : 1;
      if (aNear !== bNear) return aNear - bNear;
      return a.i - b.i;
    })
    .map((x) => x.zone);
}
