/**
 * nspd-regulation mapper tests — audit-issue #1962 «Градрегламент НСПД не выведен
 * в Раздел 1».
 *
 * Pin the field mapping the light report depends on:
 *   1. Real nspd_zoning (live shape: max_far/max_building_pct as STRINGS) →
 *      coerced rows + ВРИ list + КСИТ-ёмкость from area.
 *   2. null nspd_zoning → present:false → каллер показывает empty-state.
 *   3. coerceFloat narrows "2.4"/"80"/numbers, rejects null/""/non-numeric.
 */
import { describe, expect, it } from "vitest";

import { adaptNspdRegulation, coerceFloat } from "../nspd-regulation";
import type { NspdZoning } from "@/types/nspd";

// Live shape for 66:41:0205010:287 (POST /analyze) — max_far / max_building_pct
// arrive as strings; regulation_zone_index is the human index "ПК-1".
const LIVE_ZONING: NspdZoning = {
  zone_code: "66:00-7.155",
  zone_name: "66:00-7.155",
  source: "nspd-quarter-dump",
  max_far: "2.4",
  max_height_m: null,
  max_floors: 2,
  max_building_pct: "80",
  min_parcel_area_m2: null,
  regulation_zone_index: "ПК-1",
  regulation_source: "ekb-geoportal-urbancard",
  main_vri: [
    "3.1 Коммунальное обслуживание",
    "2.7.1\n Хранение автотранспорта",
    "  ", // whitespace-only → trimmed away
  ],
};

const AREA_M2 = 106378;

describe("coerceFloat (#1962)", () => {
  it("parses wire strings, passes numbers, rejects junk", () => {
    expect(coerceFloat("2.4")).toBe(2.4);
    expect(coerceFloat("80")).toBe(80);
    expect(coerceFloat(2)).toBe(2);
    expect(coerceFloat(null)).toBeNull();
    expect(coerceFloat(undefined)).toBeNull();
    expect(coerceFloat("")).toBeNull();
    expect(coerceFloat("n/a")).toBeNull();
  });
});

describe("adaptNspdRegulation (#1962)", () => {
  it("maps the live nspd_zoning shape into regulation rows", () => {
    const vm = adaptNspdRegulation(LIVE_ZONING, AREA_M2);

    expect(vm.present).toBe(true);
    expect(vm.zone).toBe("ПК-1");
    expect(vm.source).toBe("ekb-geoportal-urbancard");

    const byLabel = Object.fromEntries(vm.rows.map((r) => [r.label, r.value]));
    expect(byLabel["Тер. зона"]).toBe("ПК-1");
    expect(byLabel["КСИТ (max FAR)"]).toBe("2,4");
    expect(byLabel["Этажность"]).toBe("2 эт.");
    expect(byLabel["Коэф. застройки"]).toBe("80 %");
    // max_height_m null → row omitted, not a "—" with NaN.
    expect(byLabel["Пред. высота"]).toBeUndefined();
    // КСИТ-ёмкость = area × max_far = 106378 × 2.4 = 255307,2 → 255 307 (digits
    // grouped by a NBSP from ru-RU toLocaleString, hence the regex over \D).
    expect(byLabel["КСИТ-ёмкость"].replace(/\D/g, "")).toBe("255307");
    // Пятно застройки = area × 80% = 85 102.
    expect(byLabel["Пятно застройки"].replace(/\D/g, "")).toBe("85102");
    expect(byLabel["Источник"]).toBe("ekb-geoportal-urbancard");
  });

  it("trims and filters the permitted-use (ВРИ) list", () => {
    const vm = adaptNspdRegulation(LIVE_ZONING, AREA_M2);
    expect(vm.permittedUses).toEqual([
      "3.1 Коммунальное обслуживание",
      "2.7.1 Хранение автотранспорта",
    ]);
  });

  it("omits area-derived rows when geometry is unknown", () => {
    const vm = adaptNspdRegulation(LIVE_ZONING, null);
    const labels = vm.rows.map((r) => r.label);
    expect(labels).not.toContain("КСИТ-ёмкость");
    expect(labels).not.toContain("Пятно застройки");
    // зона / КСИТ / этажность / коэф. застройки / источник still present.
    expect(labels).toContain("КСИТ (max FAR)");
  });

  it("reports not-present for null nspd_zoning (empty-state)", () => {
    const vm = adaptNspdRegulation(null, AREA_M2);
    expect(vm.present).toBe(false);
    expect(vm.zone).toBeNull();
    expect(vm.rows).toHaveLength(0);
    expect(vm.permittedUses).toHaveLength(0);
  });

  it("falls back to zone_code when regulation_zone_index is absent", () => {
    const vm = adaptNspdRegulation(
      { zone_code: "Ж-2", zone_name: null },
      AREA_M2,
    );
    expect(vm.present).toBe(true);
    expect(vm.zone).toBe("Ж-2");
  });
});
