/**
 * Minimal WKT → GeoJSON parser (no new deps — see #999 conventions).
 *
 * Extracted from MarketLayers.tsx so the hand-rolled parser is unit-testable in
 * isolation (the #999 risk-zone parser had zero coverage and never ran on the QA
 * parcel). Behavior is otherwise identical to the inlined version.
 *
 * Supports the types the NSPD layers actually emit (ST_AsText, EPSG:4326,
 * `lon lat` order): POINT, LINESTRING, MULTILINESTRING, POLYGON, MULTIPOLYGON.
 * Anything else / garbage → null (the feature then simply is not drawn). NOT
 * general-purpose. LINESTRING / MULTILINESTRING были добавлены для слоя «красные
 * линии застройки» (§13, nspd_red_lines — ST_AsText на LINESTRING-геометрии);
 * POLYGON/MULTIPOLYGON покрывают risk-зоны (§13) и opportunity-ЗУ (§12.1).
 *
 * Coordinate order: WKT is `lon lat`; GeoJSON Position is `[lon, lat]` — we keep
 * that order verbatim (mismatching it is the bug class that drops markers in the
 * ocean).
 *
 * EWKT tolerance: a leading `SRID=<digits>;` prefix (case-insensitive) is
 * stripped before type detection, so `SRID=4326;POLYGON(...)` parses to the same
 * geometry as the bare `POLYGON(...)`. Backend currently emits plain WKT via
 * ST_AsText, but this is cheap defense-in-depth (per the #999 review + chip).
 */

import type { Geometry, Position } from "geojson";

// Leading `SRID=4326;` EWKT prefix (case-insensitive), if present.
const EWKT_SRID_PREFIX = /^SRID=\d+;/i;

export function parseRing(body: string): Position[] {
  return body
    .split(",")
    .map((pair) => pair.trim().split(/\s+/).map(Number))
    .filter(
      (nums): nums is [number, number] =>
        nums.length >= 2 &&
        Number.isFinite(nums[0]) &&
        Number.isFinite(nums[1]),
    )
    .map(([lon, lat]) => [lon, lat] as Position);
}

// Делит верхнеуровневые группы внутри MULTIPOLYGON по скобочной вложенности,
// чтобы запятые ВНУТРИ координат не рвали полигоны.
export function splitTopLevel(body: string): string[] {
  const parts: string[] = [];
  let depth = 0;
  let start = 0;
  for (let i = 0; i < body.length; i++) {
    const ch = body[i];
    if (ch === "(") depth++;
    else if (ch === ")") depth--;
    else if (ch === "," && depth === 0) {
      parts.push(body.slice(start, i));
      start = i + 1;
    }
  }
  parts.push(body.slice(start));
  return parts;
}

export function wktToGeometry(wkt: string): Geometry | null {
  // Strip a leading EWKT `SRID=<n>;` prefix before type detection so EWKT input
  // parses identically to plain WKT.
  const trimmed = wkt.trim().replace(EWKT_SRID_PREFIX, "");
  const upper = trimmed.toUpperCase();

  try {
    if (upper.startsWith("POINT")) {
      const body = trimmed.slice(
        trimmed.indexOf("(") + 1,
        trimmed.lastIndexOf(")"),
      );
      const nums = body.trim().split(/\s+/).map(Number);
      if (
        nums.length < 2 ||
        !Number.isFinite(nums[0]) ||
        !Number.isFinite(nums[1])
      ) {
        return null;
      }
      return { type: "Point", coordinates: [nums[0], nums[1]] };
    }

    // NB: MULTILINESTRING проверяется ДО LINESTRING (startsWith), как
    // MULTIPOLYGON до POLYGON ниже.
    if (upper.startsWith("MULTILINESTRING")) {
      // MULTILINESTRING((lon lat, …),(lon lat, …))
      const inner = trimmed.slice(
        trimmed.indexOf("(") + 1,
        trimmed.lastIndexOf(")"),
      );
      const lines = splitTopLevel(inner)
        .map((lineChunk) => parseRing(lineChunk.replace(/[()]/g, "")))
        .filter((line) => line.length >= 2);
      if (lines.length === 0) return null;
      return { type: "MultiLineString", coordinates: lines };
    }

    if (upper.startsWith("LINESTRING")) {
      // LINESTRING(lon lat, lon lat, …)
      const inner = trimmed.slice(
        trimmed.indexOf("(") + 1,
        trimmed.lastIndexOf(")"),
      );
      const coords = parseRing(inner);
      if (coords.length < 2) return null;
      return { type: "LineString", coordinates: coords };
    }

    if (upper.startsWith("MULTIPOLYGON")) {
      // MULTIPOLYGON(((ring),(hole)),((ring)))
      const inner = trimmed.slice(
        trimmed.indexOf("(") + 1,
        trimmed.lastIndexOf(")"),
      );
      const polygons = splitTopLevel(inner)
        .map((polyChunk) => {
          const polyBody = polyChunk
            .trim()
            .replace(/^\(/, "")
            .replace(/\)$/, "");
          const rings = splitTopLevel(polyBody)
            .map((ringChunk) => parseRing(ringChunk.replace(/[()]/g, "")))
            .filter((ring) => ring.length >= 3);
          return rings;
        })
        .filter((rings) => rings.length > 0);
      if (polygons.length === 0) return null;
      return { type: "MultiPolygon", coordinates: polygons };
    }

    if (upper.startsWith("POLYGON")) {
      // POLYGON((ring),(hole))
      const inner = trimmed.slice(
        trimmed.indexOf("(") + 1,
        trimmed.lastIndexOf(")"),
      );
      const rings = splitTopLevel(inner)
        .map((ringChunk) => parseRing(ringChunk.replace(/[()]/g, "")))
        .filter((ring) => ring.length >= 3);
      if (rings.length === 0) return null;
      return { type: "Polygon", coordinates: rings };
    }
  } catch {
    // Любой неожиданный WKT — graceful skip (зона не рисуется).
    return null;
  }

  return null;
}
