/*
  map-view.jsx — SHARED live-map view module (window.MapPanel / window.MapView).
  Extracted verbatim from parcel-lookup-v2.jsx so both the lookup page and the
  Kreator (index.html) can render the same map: parcel edges + buildable,
  GESUT/BDOT raster overlays, WN/SN strefy bands, vector water/power lines,
  the legend, and click-to-trace.

  Stack rules (CLAUDE.md): plain browser script, NO import/export — share via
  window.*. Load with <script type="text/babel" src="map-view.jsx"> AFTER React
  + parcel-geometry-v6.jsx and BEFORE the page that renders <MapPanel>.

  Contract:
    <MapPanel an rec big onCycle manualLines onManualLinesChange />
  - `an`  = window.ParcelGeo.analyze(engineParcel(rec, manualLines)) — the page owns this.
  - `rec` = resolved parcel record (see docs/architecture-modules.md).
  - `manualLines` is a CONTROLLED prop (the page keeps it so it folds into an/PUM);
    ephemeral view state (overlay toggles + in-progress trace) is internal to MapPanel.
  The self-injected CSS uses host CSS vars with fallbacks so it renders on any page.
*/
(function () {
  const { useState, useEffect } = React;

  // one-time self-injected CSS (host vars + fallbacks) — mirrors the map rules
  // that used to live in parcel-lookup-v2.jsx's stylesheet.
  if (!document.getElementById("mapview-css")) {
    const st = document.createElement("style");
    st.id = "mapview-css";
    st.textContent = `
  .v2-mono{font-family:"IBM Plex Mono",ui-monospace,monospace;}
  .v2-map{width:100%;display:block;background:var(--surface,#fbf9f4);border:1px solid var(--line,#ddd6c8);border-radius:4px;}
  .v2-mapwrap{position:relative;}
  .v2-mapsvg{position:relative;}
  .v2-maptag{position:absolute;left:8px;bottom:7px;font-size:9px;letter-spacing:.04em;color:var(--ink-faint,#8c8576);background:color-mix(in srgb,var(--surface,#fbf9f4) 86%,transparent);padding:1px 5px;border-radius:3px;}
  .v2-maplegend{display:flex;flex-wrap:wrap;gap:5px 13px;margin-top:6px;font-size:9.5px;line-height:1.25;color:var(--ink-soft,#5c564a);}
  .v2-maplegend .it{display:flex;align-items:center;gap:5px;white-space:nowrap;}
  .v2-maplegend .sw{flex:none;}
  .v2-maplegend .src{color:var(--ink-faint,#8c8576);}
  .v2-maplegend .vk{font-family:"IBM Plex Mono",monospace;font-weight:700;}
  .v2-front-set{font-size:9.5px;color:var(--accent-ink,#6f4a22);background:none;border:1px dashed var(--line-strong,#c7bfac);border-radius:3px;padding:3px 6px;cursor:pointer;}
  .v2-front-set:hover{border-color:var(--accent,#9a6a3a);}`;
    document.head.appendChild(st);
  }

  // edge-kind colours (mirror of parcel-lookup-v2.jsx — keep in sync) and the
  // Manual-trace strefa ½-widths (also read by the page's engineParcel via
  // window.MapPanel.MANUAL_HALF, so this file is the single source).
  const KIND_COLOR = { ulica: "var(--accent,#9a6a3a)", dzialka: "#8c8576", las: "#5e7d4f", woda: "#46708c" };
  const MANUAL_HALF = { nn: 2, SN: 5, WN: 19 };

  function MapView({ an, rec, big, onCycle, onFront, showSieci, showPower, showWody, traceMode, tracePts, manualLines, onTraceClick }) {
    if (!an || an.error) return <div className="v2-map" style={{ height: big ? 240 : 150, display: "grid", placeItems: "center", color: "var(--ink-faint)", fontSize: 11 }}>brak geometrii</div>;
    const W = big ? 360 : 300, H = big ? 250 : 168, pad = big ? 34 : 26;
    // front-frame coords (front = lowest y); flip Y so front sits at the bottom
    const rr = an.ring.map(an.fwd);
    const xs = rr.map((p) => p[0]), ys = rr.map((p) => p[1]);
    let minX = Math.min(...xs), maxX = Math.max(...xs), minY = Math.min(...ys), maxY = Math.max(...ys);
    // widen the fit so the drawn linie zabudowy are actually IN view — the
    // nearest line can run tens of metres out (canonical parcel: ~26-38 m),
    // so a fixed margin clips it away and its absence surprises. Adaptive:
    // find the nearest line vertex to the (frozen) parcel bbox, take that
    // distance +15 m as the include-radius, capped at 80 m so a far-away
    // line can't shrink the parcel into a dot.
    const r0 = { minX, maxX, minY, maxY };
    const rectDist = (p) => Math.hypot(
      Math.max(r0.minX - p[0], 0, p[0] - r0.maxX),
      Math.max(r0.minY - p[1], 0, p[1] - r0.maxY));
    const linePts = (rec.planLines || []).flatMap((ln) => ln.coords.map(an.fwd));
    // power lines close enough to draw (≤90 m) — front-frame projected
    // draw power lines: default only close ones (≤90 m); the "otoczenie" toggle
    // shows all (and widens the fit) so distant lines become visible on demand.
    const VOLT_COL = { nn: "#4a7c52", SN: "#b07d2e", WN: "#a8503e" };
    const drawPower = (rec.powerLines || []).filter((p) => showPower || p.dist <= 90)
      .map((p) => ({ label: p.label, klasa: p.klasa, dist: p.dist, pts: p.coords.map(an.fwd) }));
    // GESUT electric-class markers (nn/SN/WN) at their detected location — the
    // raster overlay is single-colour, so these carry the voltage distinction.
    // Store the front-frame point; PX is applied at render (defined below).
    const elecMarks = (showSieci ? (rec.electricGesut || []) : [])
      .filter((e) => e.xy).map((e) => ({ klasa: e.klasa, placement: e.placement, q: an.fwd(e.xy) }));
    // BDOT10k water vector (cieki/rowy/kanały) — exact geometry, drawn blue
    const WATER_COL = "#2f6ea0";
    const drawWater = (rec.waterLines || []).filter((w) => w.dist <= 120)
      .map((w) => ({ typ: w.typ, nazwa: w.nazwa, pts: w.coords.map(an.fwd) }));
    // strefy techniczne WN/SN — draw as a translucent band (½-width each side)
    // under the power line; the buildable polygon already reflects the carve-out.
    const drawStrefy = (rec.strefy || []).map((s) => ({ klasa: s.klasa, halfWidth: s.halfWidth, pts: s.coords.map(an.fwd) }));
    // hand-traced lines (Manual) + their strefa band + the in-progress trace
    const MANUAL_COL = "#6a4d8a";
    const drawManual = (manualLines || []).map((l) => ({ klasa: l.klasa, halfWidth: MANUAL_HALF[l.klasa] || 0, pts: l.coords.map(an.fwd) }));
    const traceProj = (tracePts || []).map(an.fwd);
    const fitPts = linePts.concat(drawPower.flatMap((p) => p.pts)).concat(drawWater.flatMap((w) => w.pts));
    if (fitPts.length) {
      const nearest = Math.min(...fitPts.map(rectDist));
      const NEAR = Math.min(nearest + 15, showPower ? 260 : 95);
      for (const p of fitPts) {
        if (rectDist(p) > NEAR) continue;
        if (p[0] < minX) minX = p[0]; if (p[0] > maxX) maxX = p[0];
        if (p[1] < minY) minY = p[1]; if (p[1] > maxY) maxY = p[1];
      }
    }
    const sx = (maxX - minX) || 1, sy = (maxY - minY) || 1;
    const sc = Math.min((W - 2 * pad) / sx, (H - 2 * pad) / sy);
    const ox = (W - sc * sx) / 2, oy = (H - sc * sy) / 2;
    const PX = (p) => [ox + (p[0] - minX) * sc, H - (oy + (p[1] - minY) * sc)];
    const pts = rr.map(PX);
    const n = pts.length;
    const kindOf = (i) => (rec.edges[i] && rec.edges[i].kind) || "dzialka";
    // buildable inset (the generated obszar zabudowy)
    const bp = (an.buildable && an.buildable.poly && an.buildable.poly.length >= 3)
      ? an.buildable.poly.map(an.fwd).map(PX) : null;
    // GESUT utilities raster overlay (incl. the nn line): no vector geometry is
    // published (WFS disabled), so we overlay a transparent GetMap PNG — same
    // as geoportal. The image is an axis-aligned EPSG:2180 bbox; place it with
    // an affine matrix mapping its corners through fwd→PX so it rotates to
    // match the front-frame. Best-effort: transparent/empty where no data.
    // shared: build a proxied GetMap raster placed onto the rotated front-frame
    // via an affine matrix (image corners → fwd→PX). buildUp(E0,N0,E1,N1,IW,IH)
    // returns the upstream WMS URL. Used for GESUT utilities and BDOT10k water.
    const rasterOverlay = (buildUp, PADm) => {
      const es = an.ring.map((p) => p[0]), ns = an.ring.map((p) => p[1]);
      const E0 = Math.min(...es) - PADm, E1 = Math.max(...es) + PADm;
      const N0 = Math.min(...ns) - PADm, N1 = Math.max(...ns) + PADm;
      const IW = 512, IH = Math.max(64, Math.round(512 * (N1 - N0) / (E1 - E0)));
      const href = "/api/gugik?url=" + encodeURIComponent(buildUp(E0, N0, E1, N1, IW, IH));
      const S = (E, N) => PX(an.fwd([E, N]));
      const s0 = S(E0, N1), s1 = S(E1, N1), s2 = S(E0, N0); // image (0,0),(IW,0),(0,IH)
      return { href, IW, IH, mat: [(s1[0]-s0[0])/IW, (s1[1]-s0[1])/IW, (s2[0]-s0[0])/IH, (s2[1]-s0[1])/IH, s0[0], s0[1]] };
    };
    let sieci = null;
    if (showSieci && rec.geomSrc === "live") {
      const teryt4 = String(rec.teryt || "").slice(0, 4);
      const layers = "siec_wodociagowa,siec_kanalizacyjna,siec_gazowa,siec_elektroenergetyczna,siec_telekomunikacyjna,siec_cieplownicza";
      sieci = rasterOverlay((E0, N0, E1, N1, IW, IH) =>
        "https://wms.epodgik.pl/cgi-bin/gesut/" + teryt4
        + "?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&LAYERS=" + layers + "&STYLES=&SRS=EPSG:2180&BBOX="
        + [E0, N0, E1, N1].join(",") + "&WIDTH=" + IW + "&HEIGHT=" + IH + "&FORMAT=image/png&TRANSPARENT=TRUE", 45);
    }
    // BDOT10k water (national, free, open data): cieki (rz/rzOk), kanały
    // (kan/kanOkr), rowy melioracyjne (row), woda powierzchniowa (WPow).
    let wody = null;
    if (showWody && rec.geomSrc === "live") {
      const layers = "rz,rzOk,kan,kanOkr,row,WPow";
      wody = rasterOverlay((E0, N0, E1, N1, IW, IH) =>
        "https://mapy.geoportal.gov.pl/wss/service/pub/guest/kompozycja_BDOT10k_WMS/MapServer/WMSServer"
        + "?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&LAYERS=" + layers + "&STYLES=&SRS=EPSG:2180&BBOX="
        + [E0, N0, E1, N1].join(",") + "&WIDTH=" + IW + "&HEIGHT=" + IH + "&FORMAT=image/png&TRANSPARENT=TRUE", 80);
    }
    // ---- map symbol legend: one entry per layer actually visible ----------
    // Swatches drawn as tiny inline SVGs so line/dash/dot styles read at a glance.
    const lineSw = (col, dash) => (
      <svg className="sw" width="20" height="8"><line x1="1" y1="4" x2="19" y2="4" stroke={col} strokeWidth="2" strokeDasharray={dash || ""} strokeLinecap="round" /></svg>);
    const dotSw = (col) => (
      <svg className="sw" width="12" height="12"><circle cx="6" cy="6" r="3.4" fill={col} stroke={col} strokeWidth="1.4" /></svg>);
    const rectSw = (col) => (
      <svg className="sw" width="12" height="12"><rect x="1" y="1" width="10" height="10" rx="2" fill={col} fillOpacity="0.45" stroke={col} strokeWidth="0.9" /></svg>);
    const hatchSw = () => (
      <svg className="sw" width="12" height="12"><rect x="1" y="1" width="10" height="10" fill="none" stroke="var(--accent)" strokeWidth="0.9" strokeDasharray="2 1.5" /><line x1="1" y1="11" x2="11" y2="1" stroke="var(--accent)" strokeWidth="0.7" opacity="0.6" /></svg>);
    const powerKlasy = [...new Set(drawPower.map((p) => p.klasa))];
    const gesutKlasy = [...new Set(elecMarks.map((m) => m.klasa))];
    const legendItems = [];
    if (drawWater.length) legendItems.push({ sw: lineSw(WATER_COL), t: "ciek / rów / kanał", src: "BDOT10k · wektor" });
    if (drawPower.length) legendItems.push({
      sw: lineSw(VOLT_COL[powerKlasy[0]] || "#c86a1a", "2 2.5"),
      t: <span>linia el. <span className="vk" style={{ color: VOLT_COL[powerKlasy.includes("WN") ? "WN" : powerKlasy[0]] }}>{powerKlasy.join("/")}</span></span>,
      src: "BDOT10k · wektor",
    });
    if (elecMarks.length) legendItems.push({ sw: dotSw(VOLT_COL[gesutKlasy[0]] || "#b07d2e"), t: "punkt sieci el. " + gesutKlasy.join("/"), src: "GESUT · orientacyjnie" });
    if (drawStrefy.length) legendItems.push({ sw: rectSw(VOLT_COL[drawStrefy[0].klasa] || "#a8503e"), t: "strefa techn. (pas WN/SN)", src: "orientacyjnie · wycięta z zabudowy", title: "pas technologiczny WN ≈19 m / SN ≈5 m od osi — typowe wartości operatorskie, wycięte z obszaru zabudowy" });
    if (drawManual.length) legendItems.push({ sw: lineSw(MANUAL_COL, "4 2"), t: "linia ręczna (" + [...new Set(drawManual.map((l) => l.klasa))].join("/") + ")", src: "Manual · obrys z rastra + strefa" });
    if (rec.planLines && rec.planLines.length) legendItems.push({ sw: lineSw("var(--low)", "6 3"), t: "linia zabudowy", src: "plan · wektor WFS" });
    if (sieci) legendItems.push({ sw: rectSw("var(--ink-faint)"), t: "sieci uzbrojenia (W·K·G·E·T·C)", src: "GESUT raster · orient.", title: "raster GESUT: W niebieski · K brązowy · G żółty · E czerwony · T fioletowy · C — orientacyjnie" });
    if (wody) legendItems.push({ sw: rectSw(WATER_COL), t: "wody", src: "BDOT10k raster" });
    if (bp) legendItems.push({ sw: hatchSw(), t: "obszar zabudowy", src: "wynik" });
    return (
      <>
      <div className="v2-mapsvg">
      <svg className="v2-map" viewBox={"0 0 " + W + " " + H} style={{ height: H, cursor: traceMode ? "crosshair" : undefined }}
        onClick={traceMode ? (e) => {
          const svg = e.currentTarget, m = svg.getScreenCTM(); if (!m) return;
          const pt = svg.createSVGPoint(); pt.x = e.clientX; pt.y = e.clientY;
          const uc = pt.matrixTransform(m.inverse());
          const fx = (uc.x - ox) / sc + minX, fy = ((H - uc.y) - oy) / sc + minY;
          onTraceClick && onTraceClick(an.inv([fx, fy]));
        } : undefined}>
        <defs>
          <pattern id={"v2grid" + (big ? "b" : "s")} width="14" height="14" patternUnits="userSpaceOnUse">
            <path d="M14 0 L0 0 0 14" fill="none" stroke="var(--line-strong)" strokeWidth="0.4" opacity="0.35" />
          </pattern>
          <pattern id={"v2hatch" + (big ? "b" : "s")} width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
            <line x1="0" y1="0" x2="0" y2="6" stroke="var(--accent)" strokeWidth="0.8" opacity="0.4" />
          </pattern>
        </defs>
        <rect width={W} height={H} fill={"url(#v2grid" + (big ? "b" : "s") + ")"} />
        <polygon points={pts.map((p) => p[0].toFixed(1) + "," + p[1].toFixed(1)).join(" ")}
          fill="color-mix(in srgb,var(--accent) 7%,transparent)" stroke="none" />
        {wody && <image href={wody.href} width={wody.IW} height={wody.IH} preserveAspectRatio="none"
          transform={"matrix(" + wody.mat.map((v) => v.toFixed(5)).join(" ") + ")"} opacity="0.9"
          style={{ pointerEvents: "none" }} />}
        {sieci && <image href={sieci.href} width={sieci.IW} height={sieci.IH} preserveAspectRatio="none"
          transform={"matrix(" + sieci.mat.map((v) => v.toFixed(5)).join(" ") + ")"} opacity="0.85"
          style={{ pointerEvents: "none" }} />}
        {/* strefy techniczne WN/SN: translucent band = the pas technologiczny that
            the buildable field carves out (drawn under the power line + edges) */}
        {drawStrefy.map((s, i) => {
          const pj = s.pts.map(PX);
          return (
            <polyline key={"st" + i} points={pj.map((q) => q[0].toFixed(1) + "," + q[1].toFixed(1)).join(" ")}
              fill="none" stroke={VOLT_COL[s.klasa] || "#a8503e"} strokeWidth={Math.max(2, 2 * s.halfWidth * sc)}
              strokeLinecap="round" strokeLinejoin="round" opacity="0.16">
              <title>{"strefa techniczna linii " + s.klasa + " — pas ~" + s.halfWidth + " m od osi (orientacyjnie, wycięta z obszaru zabudowy)"}</title>
            </polyline>
          );
        })}
        {/* hand-traced lines (Manual): strefa band + the line itself */}
        {drawManual.map((l, i) => { const pj = l.pts.map(PX); const d = pj.map((q) => q[0].toFixed(1) + "," + q[1].toFixed(1)).join(" ");
          return (
            <g key={"mn" + i}>
              {l.halfWidth > 0 && <polyline points={d} fill="none" stroke={MANUAL_COL} strokeWidth={Math.max(2, 2 * l.halfWidth * sc)} strokeLinecap="round" strokeLinejoin="round" opacity="0.15" />}
              <polyline points={d} fill="none" stroke={MANUAL_COL} strokeWidth="1.8" strokeDasharray="4 2" opacity="0.95">
                <title>{"linia " + l.klasa + " — obrysowana ręcznie (Manual), strefa ~" + l.halfWidth + " m (orientacyjnie)"}</title>
              </polyline>
            </g>
          );
        })}
        {/* drawn linie zabudowy from the plan vector (WFS lay22) — same data the
            geoportal renders; parts outside the viewBox clip away naturally */}
        {(rec.planLines || []).map((ln, i) => (
          <polyline key={"pl" + i}
            points={ln.coords.map(an.fwd).map(PX).map((p) => p[0].toFixed(1) + "," + p[1].toFixed(1)).join(" ")}
            fill="none" stroke="var(--low)" strokeWidth="1.5" strokeDasharray="7 3" opacity="0.7">
            <title>{(ln.opis || "linia zabudowy") + (ln.plan ? " · plan " + ln.plan : "") + " (wektor WFS)"}</title>
          </polyline>
        ))}
        {drawWater.map((w, i) => {
          const pj = w.pts.map(PX);
          return (
            <polyline key={"wt" + i} points={pj.map((q) => q[0].toFixed(1) + "," + q[1].toFixed(1)).join(" ")}
              fill="none" stroke={WATER_COL} strokeWidth="1.6" opacity="0.9">
              <title>{(w.typ === "rów" ? "rów melioracyjny" : w.typ) + (w.nazwa ? " „" + w.nazwa + "”" : "") + " (BDOT10k)"}</title>
            </polyline>
          );
        })}
        {drawPower.map((p, i) => {
          const pj = p.pts.map(PX);
          return (
            <polyline key={"pw" + i} points={pj.map((q) => q[0].toFixed(1) + "," + q[1].toFixed(1)).join(" ")}
              fill="none" stroke={VOLT_COL[p.klasa] || "#c86a1a"} strokeWidth="1.8" strokeDasharray="2 3" opacity="0.95">
              <title>{p.label + " (" + (p.dist) + " m)"}</title>
            </polyline>
          );
        })}
        {elecMarks.map((m, i) => {
          const mp = PX(m.q);
          return (
          <g key={"em" + i}>
            <circle cx={mp[0]} cy={mp[1]} r="3.4" fill={m.placement === "napowietrzna" ? "#fff" : VOLT_COL[m.klasa]} stroke={VOLT_COL[m.klasa]} strokeWidth="1.6" opacity="0.95">
              <title>{"GESUT (orientacyjnie): prąd — dekodowana klasa " + m.klasa + (m.placement ? " " + m.placement : "") + ". Klasa/przebieg bywają błędne, pozycja przybliżona — wiążąca mapa zasadnicza."}</title>
            </circle>
            <text x={mp[0] + 5} y={mp[1] + 3} fontFamily="IBM Plex Mono,monospace" fontSize="7.5" fontWeight="700" fill={VOLT_COL[m.klasa]}>{m.klasa}{m.placement === "napowietrzna" ? " ↑" : m.placement === "doziemna" ? " ↓" : ""}</text>
          </g>
          );
        })}
        {bp && <polygon points={bp.map((p) => p[0].toFixed(1) + "," + p[1].toFixed(1)).join(" ")}
          fill={"url(#v2hatch" + (big ? "b" : "s") + ")"} stroke="var(--accent)" strokeWidth="1" strokeDasharray="3 2" opacity="0.85" />}
        {pts.map((a, i) => {
          const b = pts[(i + 1) % n], k = kindOf(i), isFront = i === rec.frontIdx;
          const mx = (a[0] + b[0]) / 2, my = (a[1] + b[1]) / 2;
          return (
            <g key={i} style={{ cursor: traceMode ? "crosshair" : "pointer" }} onClick={() => { if (traceMode) return; onCycle && onCycle(i); }}>
              <line x1={a[0]} y1={a[1]} x2={b[0]} y2={b[1]} stroke="transparent" strokeWidth="14" />
              <line x1={a[0]} y1={a[1]} x2={b[0]} y2={b[1]} stroke={KIND_COLOR[k]} strokeWidth={isFront ? 4 : 2.4} strokeLinecap="round" />
              <g transform={"translate(" + mx + "," + my + ")"}>
                <circle r="8" fill={KIND_COLOR[k]} />
                <text textAnchor="middle" dy="3.2" fontFamily="IBM Plex Mono,monospace" fontSize="9" fontWeight="700" fill="#fff">{String.fromCharCode(65 + i)}</text>
              </g>
            </g>
          );
        })}
        {/* front marker */}
        {(() => { const a = pts[rec.frontIdx], b = pts[(rec.frontIdx + 1) % n]; const mx = (a[0] + b[0]) / 2, my = (a[1] + b[1]) / 2;
          return <text x={mx} y={my + 19} textAnchor="middle" fontFamily="IBM Plex Sans,sans-serif" fontSize="8.5" fontWeight="700" letterSpacing="0.1em" fill="var(--accent-ink)">FRONT</text>; })()}
        {/* in-progress trace: dashed connector + clicked points (drawn on top) */}
        {traceMode && traceProj.length > 0 && (() => { const tp = traceProj.map(PX); return (
          <g style={{ pointerEvents: "none" }}>
            {tp.length > 1 && <polyline points={tp.map((q) => q[0].toFixed(1) + "," + q[1].toFixed(1)).join(" ")} fill="none" stroke={MANUAL_COL} strokeWidth="1.6" strokeDasharray="3 2" opacity="0.9" />}
            {tp.map((q, i) => <circle key={"tp" + i} cx={q[0]} cy={q[1]} r="2.6" fill="#fff" stroke={MANUAL_COL} strokeWidth="1.4" />)}
          </g>
        ); })()}
      </svg>
      <span className="v2-maptag v2-mono">EPSG:2180{bp ? " · obszar zabudowy" : ""}</span>
      </div>
      {legendItems.length > 0 && (
        <div className="v2-maplegend">
          {legendItems.map((it, i) => (
            <span className="it" key={i} title={it.title || undefined}>{it.sw}<span>{it.t} <span className="src">· {it.src}</span></span></span>
          ))}
        </div>
      )}
      </>
    );
  }

  // MapPanel — the reusable unit: owns ephemeral view state (overlay toggles +
  // in-progress trace) and renders the toggle bar + the map. `manualLines` is a
  // CONTROLLED prop so the host page can fold it into `an`/PUM.
  function MapPanel({ an, rec, big, onCycle, manualLines, onManualLinesChange }) {
    const [showSieci, setShowSieci] = useState(false);
    const [showPower, setShowPower] = useState(false);
    const [showWody, setShowWody] = useState(false);
    const [traceMode, setTraceMode] = useState(false);
    const [tracePts, setTracePts] = useState([]);
    // reset the trace on a parcel change (matches the old page behaviour; the
    // overlay toggles deliberately persist, as they did before).
    useEffect(() => { setTraceMode(false); setTracePts([]); }, [rec && rec.teryt]);
    const ml = manualLines || [];
    const setML = onManualLinesChange || (() => {});
    const addTracePt = (xy) => setTracePts((p) => [...p, xy]);
    const undoTracePt = () => setTracePts((p) => p.slice(0, -1));
    const saveTrace = (klasa) => { if (tracePts.length >= 2) setML([...ml, { coords: tracePts, klasa }]); setTracePts([]); setTraceMode(false); };
    const cancelTrace = () => { setTracePts([]); setTraceMode(false); };

    const mapToggles = rec && rec.geomSrc === "live" ? (
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap", margin: "0 0 5px" }}>
        <button className="v2-front-set" style={{ fontSize: 10 }} onClick={() => setShowSieci((v) => !v)}
          title="nałóż sieci uzbrojenia terenu (GESUT) — wodociąg, kanalizacja, gaz, prąd (w tym linia nn), telekom, ciepło">
          {showSieci ? "✓ sieci uzbrojenia" : "＋ sieci uzbrojenia"}</button>
        <button className="v2-front-set" style={{ fontSize: 10 }} onClick={() => setShowWody((v) => !v)}
          title="nałóż wody powierzchniowe z BDOT10k — cieki (rzeki/strumienie), rowy melioracyjne, kanały (dane krajowe, darmowe)">
          {showWody ? "✓ wody/cieki" : "＋ wody/cieki"}</button>
        {rec.powerLines && rec.powerLines.length > 0 && (
          <button className="v2-front-set" style={{ fontSize: 10 }} onClick={() => setShowPower((v) => !v)}
            title="poszerz widok i narysuj napowietrzne linie WN/SN z okolicy (do ~220 m)">
            {showPower ? "✓ linie napow." : "＋ linie napow."}</button>)}
        <button className="v2-front-set" style={{ fontSize: 10, borderColor: traceMode ? "var(--accent)" : undefined, color: traceMode ? "var(--accent-ink)" : undefined }}
          onClick={() => { if (traceMode) { cancelTrace(); } else { setShowSieci(true); setTracePts([]); setTraceMode(true); } }}
          title="obrysuj linię widoczną na rastrze (np. nn, której nie ma w danych wektorowych) — klikaj kolejne punkty; zapiszemy jako linię ręczną (Manual) + strefę">
          {traceMode ? "✕ anuluj obrys" : "✏ obrysuj linię"}</button>
        {traceMode && (
          <span style={{ fontSize: 10, display: "inline-flex", alignItems: "center", gap: 5, color: "var(--ink-soft)" }}>
            <b>{tracePts.length}</b> pkt · klikaj wzdłuż linii · zapisz jako:
            {["nn", "SN", "WN"].map((k) => (
              <button key={k} className="v2-front-set" style={{ fontSize: 10 }} disabled={tracePts.length < 2} onClick={() => saveTrace(k)}>{k}</button>
            ))}
            <button className="v2-front-set" style={{ fontSize: 10 }} disabled={!tracePts.length} onClick={undoTracePt}>cofnij</button>
          </span>
        )}
        {ml.length > 0 && !traceMode && (
          <button className="v2-front-set" style={{ fontSize: 10 }} onClick={() => setML([])}
            title="usuń ręcznie obrysowane linie">✕ ręczne ({ml.length})</button>)}
      </div>
    ) : null;

    return (
      <React.Fragment>
        {mapToggles}
        <div className="v2-mapwrap">
          <MapView an={an} rec={rec} big={big} onCycle={onCycle}
            showSieci={showSieci} showPower={showPower} showWody={showWody}
            traceMode={traceMode} tracePts={tracePts} manualLines={ml} onTraceClick={addTracePt} />
        </div>
      </React.Fragment>
    );
  }

  MapPanel.MANUAL_HALF = MANUAL_HALF; // single source for the page's engineParcel
  // Persist traced Manual lines per TERYT (localStorage) so they survive reloads
  // and cross both pages (lookup ⇄ Kreator). Best-effort; storage errors are swallowed.
  const MANUAL_KEY = (teryt) => "teren_manual_" + String(teryt || "").replace(/[^0-9_.\/]/g, "");
  MapPanel.loadManual = (teryt) => { try { return (teryt && JSON.parse(localStorage.getItem(MANUAL_KEY(teryt)))) || []; } catch (e) { return []; } };
  MapPanel.saveManual = (teryt, lines) => { try { if (teryt) localStorage.setItem(MANUAL_KEY(teryt), JSON.stringify(lines || [])); } catch (e) {} };
  window.MapPanel = MapPanel;
  window.MapView = MapView;
})();
