// Карта: Web Mercator. Canvas-основа (тайлы OSM/CARTO или векторная схема,
// сетка, трассы, swath, терминатор) + SVG-overlay (станции, «сейчас»,
// контуры съёмки, сброс, hover). Зум/панорама как у slippy-карты.
const { useRef: useRefMV, useState: useStateMV, useEffect: useEffectMV, useMemo: useMemoMV, useCallback } = React;

const STATIONS = window.WorldGeo.GROUND_STATIONS;
const BM = window.Basemap;
const TILE = BM.TILE;

function MapView(props) {
  const { scList, selectedPlan, window: win, now, layers, onSelectSc, paramsBy, scById, basemap,
    pickMode, onPickPoint, targets } = props;
  const wrapRef = useRefMV(null);
  const canvasRef = useRefMV(null);
  const [dims, setDims] = useStateMV({ w: 1000, h: 560 });
  const [view, setView] = useStateMV({ centerLon: 45, centerLat: 30, z: 2.2, init: false });
  const [hover, setHover] = useStateMV(null);
  const [tileTick, setTileTick] = useStateMV(0);
  const drag = useRefMV(null);
  const provider = BM.PROVIDERS[basemap] || BM.PROVIDERS.dark;

  const ws = useMemoMV(() => TILE * Math.pow(2, view.z), [view.z]);

  // Инициализация: вписать мир по ширине.
  useEffectMV(() => {
    if (view.init || !dims.w) return;
    const z = Math.max(0.7, Math.log2(dims.w / TILE) - 0.05);
    setView((v) => ({ ...v, z, centerLon: 45, centerLat: 28, init: true }));
  }, [dims, view.init]);

  const project = useCallback((lon, lat) => {
    const cwx = BM.lonToWorldX(view.centerLon, ws);
    const cwy = BM.latToWorldY(view.centerLat, ws);
    let wx = BM.lonToWorldX(lon, ws);
    // выбрать ближайшую копию мира по X (горизонтальный повтор)
    const half = ws / 2;
    while (wx - cwx > half) wx -= ws;
    while (wx - cwx < -half) wx += ws;
    return {
      x: dims.w / 2 + (wx - cwx),
      y: dims.h / 2 + (BM.latToWorldY(lat, ws) - cwy),
    };
  }, [view.centerLon, view.centerLat, ws, dims]);

  const unproject = useCallback((sx, sy) => {
    const cwx = BM.lonToWorldX(view.centerLon, ws);
    const cwy = BM.latToWorldY(view.centerLat, ws);
    return {
      lon: BM.worldXToLon(cwx + (sx - dims.w / 2), ws),
      lat: BM.worldYToLat(cwy + (sy - dims.h / 2), ws),
    };
  }, [view.centerLon, view.centerLat, ws, dims]);

  // ResizeObserver
  useEffectMV(() => {
    const el = wrapRef.current;
    if (!el) return;
    const ro = new ResizeObserver(() => setDims({ w: el.clientWidth, h: el.clientHeight }));
    ro.observe(el);
    setDims({ w: el.clientWidth, h: el.clientHeight });
    return () => ro.disconnect();
  }, []);

  // Отрисовка canvas.
  useEffectMV(() => {
    const cv = canvasRef.current;
    if (!cv) return;
    const dpr = Math.min(2, window.devicePixelRatio || 1);
    cv.width = dims.w * dpr; cv.height = dims.h * dpr;
    cv.style.width = dims.w + "px"; cv.style.height = dims.h + "px";
    const ctx = cv.getContext("2d");
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, dims.w, dims.h);

    // фон
    ctx.fillStyle = provider.dark ? "#0a1410" : "#0a1410";
    ctx.fillRect(0, 0, dims.w, dims.h);

    const P = (lon, lat) => project(lon, lat);

    if (provider.url) {
      // растровая подложка (тайлы)
      BM.drawTiles(ctx, provider, view, dims, () => setTileTick((t) => t + 1));
    } else {
      // векторная схема (оффлайн)
      drawVectorWorld(ctx, P, dims);
    }

    // сетка координат поверх (тонкая)
    ctx.strokeStyle = provider.dark ? "rgba(120,180,150,0.10)" : "rgba(255,255,255,0.14)";
    ctx.lineWidth = 1;
    ctx.beginPath();
    for (let lon = -180; lon <= 180; lon += 30) { const a = P(lon, 84), b = P(lon, -84); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); }
    for (let lat = -60; lat <= 60; lat += 30) { const a = P(-180, lat), b = P(180, lat); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); }
    ctx.stroke();

    // терминатор / ночь
    if (layers.terminator) {
      const tc = terminatorCurve(now);
      ctx.beginPath();
      tc.curve.forEach((pt, i) => { const q = P(pt[0], pt[1]); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
      const topLat = tc.nightInNorth ? 84 : -84;
      const c1 = P(180, topLat), c2 = P(-180, topLat);
      ctx.lineTo(c1.x, c1.y); ctx.lineTo(c2.x, c2.y);
      ctx.closePath();
      ctx.fillStyle = "rgba(4,8,12,0.42)";
      ctx.fill();
      ctx.strokeStyle = "rgba(255,210,120,0.30)";
      ctx.lineWidth = 1;
      ctx.beginPath();
      tc.curve.forEach((pt, i) => { const q = P(pt[0], pt[1]); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
      ctx.stroke();
    }

    // трассы + swath
    const TYPE_RGB = { optical: [126,222,180], radar: [232,200,120], combo: [240,150,165] };
    const many = scList.length;
    const spanHrs = (win.t1 - win.t0) / 3600000;
    const showSwath = layers.swath && many <= 8 && spanHrs <= 16;
    const trackAlpha = many > 40 ? 0.14 : many > 14 ? 0.22 : many > 4 ? 0.4 : 0.72;
    for (const sc of scList) {
      const p = paramsBy[sc.id];
      const rgb = TYPE_RGB[sc.type];
      const segs = groundTrack(p, win.t0, win.t1, 900);
      const isSelSc = selectedPlan && selectedPlan.scId === sc.id;
      if (showSwath || isSelSc) {
        for (const seg of segs) {
          if (!seg.length) continue;
          const midLat = seg[Math.floor(seg.length / 2)].lat;
          const mpp = BM.metersPerPixel(midLat, ws);
          const wpx = Math.max(1.5, (p.swathKm * 1000) / mpp);
          ctx.strokeStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${isSelSc ? 0.16 : 0.09})`;
          ctx.lineWidth = wpx; ctx.lineCap = "round"; ctx.lineJoin = "round";
          ctx.beginPath();
          seg.forEach((pt, i) => { const q = P(pt.lon, pt.lat); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
          ctx.stroke();
        }
      }
      if (layers.tracks) {
        const selAlpha = spanHrs > 30 ? 0.5 : spanHrs > 14 ? 0.66 : 0.92;
        ctx.strokeStyle = `rgba(${rgb[0]},${rgb[1]},${rgb[2]},${isSelSc ? selAlpha : trackAlpha})`;
        ctx.lineWidth = isSelSc ? (spanHrs > 30 ? 1 : 1.4) : many > 14 ? 0.6 : 0.9;
        for (const seg of segs) {
          ctx.beginPath();
          seg.forEach((pt, i) => { const q = P(pt.lon, pt.lat); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
          ctx.stroke();
        }
      }
    }
  }, [dims, view, scList, selectedPlan, win, now, layers, project, ws, provider, tileTick]);

  // ---- overlay (SVG) ----
  const planOverlay = useMemoMV(() => {
    if (!selectedPlan) return null;
    const p = paramsBy[selectedPlan.scId];
    const sc = scById[selectedPlan.scId];
    const shoots = [], downs = [];
    for (const wk of selectedPlan.works) {
      if (wk.kind === "shoot" && layers.shoot) {
        const pts = trackPoints(p, wk.start, wk.end, 18);
        const poly = ribbon(pts, p.swathKm / 2);
        shoots.push({ id: wk.id, label: wk.label, poly: poly.map(c => project(c[0], c[1])) });
      }
      if (wk.kind === "downlink" && layers.downlink) {
        const pts = trackPoints(p, wk.start, wk.end, 14);
        const mid = pts[Math.floor(pts.length / 2)];
        const ns = nearestStation(mid.lat, mid.lon, STATIONS);
        downs.push({
          id: wk.id, label: wk.label,
          line: pts.map(pt => project(pt.lon, pt.lat)),
          st: ns.st, stPt: project(ns.st.lon, ns.st.lat), midPt: project(mid.lon, mid.lat),
        });
      }
    }
    return { shoots, downs, scType: sc.type };
  }, [selectedPlan, layers, project, paramsBy, scById]);

  const nowMarkers = useMemoMV(() => {
    if (now < win.t0 || now > win.t1) return [];
    return scList.map((sc) => {
      const s = subPoint(paramsBy[sc.id], now);
      return { id: sc.id, short: sc.short, type: sc.type, ...project(s.lon, s.lat) };
    });
  }, [scList, now, win, project, paramsBy]);

  const TYPE_HEX = { optical: "var(--t-optical)", radar: "var(--t-radar)", combo: "var(--t-combo)" };

  // ---- pan / zoom ----
  const onWheel = (e) => {
    e.preventDefault();
    const rect = wrapRef.current.getBoundingClientRect();
    const mx = e.clientX - rect.left, my = e.clientY - rect.top;
    const before = unproject(mx, my);
    setView((v) => {
      const nz = Math.max(0.7, Math.min(9, v.z + (e.deltaY < 0 ? 0.4 : -0.4)));
      const nws = TILE * Math.pow(2, nz);
      const cwx = BM.lonToWorldX(before.lon, nws) - (mx - dims.w / 2);
      const cwy = BM.latToWorldY(before.lat, nws) - (my - dims.h / 2);
      return { ...v, z: nz, centerLon: BM.worldXToLon(cwx, nws), centerLat: BM.worldYToLat(cwy, nws) };
    });
  };
  const onDown = (e) => {
    drag.current = { x: e.clientX, y: e.clientY, lon: view.centerLon, lat: view.centerLat, moved: false };
  };
  const onMove = (e) => {
    if (!drag.current) return;
    const dx = e.clientX - drag.current.x, dy = e.clientY - drag.current.y;
    if (Math.abs(dx) + Math.abs(dy) > 3) drag.current.moved = true;
    const cwx = BM.lonToWorldX(drag.current.lon, ws) - dx;
    const cwy = BM.latToWorldY(drag.current.lat, ws) - dy;
    setView((v) => ({ ...v, centerLon: BM.worldXToLon(cwx, ws), centerLat: Math.max(-82, Math.min(82, BM.worldYToLat(cwy, ws))) }));
  };
  const onUp = (e) => {
    if (drag.current && pickMode && !drag.current.moved && onPickPoint && e) {
      const rect = wrapRef.current.getBoundingClientRect();
      const ll = unproject(e.clientX - rect.left, e.clientY - rect.top);
      onPickPoint(ll);
    }
    drag.current = null;
  };
  const zoomBy = (f) => setView((v) => ({ ...v, z: Math.max(0.7, Math.min(9, v.z + f)) }));
  const resetView = () => setView((v) => ({ ...v, init: false }));

  return (
    <div ref={wrapRef} style={{ position: "absolute", inset: 0, overflow: "hidden", cursor: pickMode ? "crosshair" : (drag.current ? "grabbing" : "grab"), background: "#0a1410" }}
      onWheel={onWheel} onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp} onMouseLeave={onUp}>
      <canvas ref={canvasRef} style={{ position: "absolute", inset: 0 }} />

      <svg style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none", overflow: "visible" }}>
        {/* выбранные наземные цели */}
        {targets && targets.map((tg) => {
          const q = project(tg.lon, tg.lat);
          return (
            <g key={tg.id} transform={`translate(${q.x},${q.y})`}>
              <circle r="9" fill="none" stroke="var(--now)" strokeWidth="1.4" opacity="0.7" />
              <line x1="-13" y1="0" x2="13" y2="0" stroke="var(--now)" strokeWidth="1" />
              <line x1="0" y1="-13" x2="0" y2="13" stroke="var(--now)" strokeWidth="1" />
              <circle r="2.5" fill="var(--now)" />
              <text x="11" y="-7" fontSize="10" fill="var(--now)" style={{ fontFamily: "var(--mono)", paintOrder: "stroke", stroke: "#0a1410", strokeWidth: 3 }}>{tg.name}</text>
            </g>
          );
        })}
        {planOverlay && planOverlay.shoots.map((s) => (
          <polygon key={s.id} points={s.poly.map(p => `${p.x},${p.y}`).join(" ")}
            fill={`color-mix(in oklch, ${TYPE_HEX[planOverlay.scType]} 32%, transparent)`}
            stroke={TYPE_HEX[planOverlay.scType]} strokeWidth="1.4" style={{ pointerEvents: "all", cursor: "pointer" }}
            onMouseEnter={(e) => setHover({ x: e.clientX, y: e.clientY, title: s.label, kind: "Съёмка / контур маршрута" })}
            onMouseLeave={() => setHover(null)} />
        ))}
        {planOverlay && planOverlay.downs.map((d) => (
          <g key={d.id}>
            <line x1={d.midPt.x} y1={d.midPt.y} x2={d.stPt.x} y2={d.stPt.y} stroke="var(--accent)" strokeWidth="1" strokeDasharray="2 3" opacity="0.6" />
            <polyline points={d.line.map(p => `${p.x},${p.y}`).join(" ")} fill="none"
              stroke="var(--accent)" strokeWidth="3.4" strokeLinecap="round"
              style={{ pointerEvents: "all", cursor: "pointer", filter: "drop-shadow(0 0 4px var(--accent))" }}
              onMouseEnter={(e) => setHover({ x: e.clientX, y: e.clientY, title: `${d.label} → ${d.st.name}`, kind: "Сброс на НКПОР" })}
              onMouseLeave={() => setHover(null)} />
          </g>
        ))}

        {layers.stations && STATIONS.map((st) => {
          const q = project(st.lon, st.lat);
          const active = planOverlay && planOverlay.downs.some(d => d.st.id === st.id);
          return (
            <g key={st.id} transform={`translate(${q.x},${q.y})`} style={{ pointerEvents: "all", cursor: "default" }}
              onMouseEnter={(e) => setHover({ x: e.clientX, y: e.clientY, title: st.name, kind: "Станция приёма (НКПОР)" })}
              onMouseLeave={() => setHover(null)}>
              <path d="M0,-6 L5,4 L-5,4 Z" fill={active ? "var(--accent)" : "rgba(190,210,220,0.92)"} stroke="#0a1410" strokeWidth="1" />
              {view.z > 3.6 && <text x="7" y="3" fontSize="9" fill={provider.dark ? "var(--ink-2)" : "#dfeaf0"} style={{ fontFamily: "var(--mono)", paintOrder: "stroke", stroke: "#0a1410", strokeWidth: 2 }}>{st.name}</text>}
            </g>
          );
        })}

        {nowMarkers.map((m) => (
          <g key={m.id} transform={`translate(${m.x},${m.y})`} style={{ pointerEvents: "all", cursor: "pointer" }}
            onClick={() => onSelectSc(m.id)}
            onMouseEnter={(e) => setHover({ x: e.clientX, y: e.clientY, title: m.short, kind: "Подспутниковая точка · сейчас" })}
            onMouseLeave={() => setHover(null)}>
            <circle r="4.5" fill={TYPE_HEX[m.type]} stroke="#0a1410" strokeWidth="1.5" />
            <circle r="8" fill="none" stroke={TYPE_HEX[m.type]} strokeWidth="1" opacity="0.5" />
          </g>
        ))}
      </svg>

      {hover && (
        <div style={{ position: "fixed", left: hover.x + 12, top: hover.y + 12, zIndex: 50, pointerEvents: "none",
          background: "var(--bg-0)", border: "1px solid var(--line)", borderRadius: 6, padding: "6px 9px", boxShadow: "var(--shadow)", maxWidth: 220 }}>
          <div style={{ fontSize: 12, fontWeight: 600, color: "var(--ink)" }}>{hover.title}</div>
          <div className="mono" style={{ fontSize: 9.5, color: "var(--ink-3)", marginTop: 2 }}>{hover.kind}</div>
        </div>
      )}

      {/* атрибуция подложки */}
      <div className="mono" style={{ position: "absolute", left: 8, bottom: 6, fontSize: 9, color: provider.dark ? "var(--ink-3)" : "rgba(255,255,255,0.8)", background: "rgba(10,20,16,0.55)", padding: "2px 6px", borderRadius: 4, pointerEvents: "none" }}>{provider.label}</div>

      <div style={{ position: "absolute", right: 12, bottom: 12, display: "flex", flexDirection: "column", gap: 4 }}>
        <ZoomBtn onClick={() => zoomBy(0.5)}>+</ZoomBtn>
        <ZoomBtn onClick={() => zoomBy(-0.5)}>−</ZoomBtn>
        <ZoomBtn onClick={resetView} title="Сбросить вид">⊡</ZoomBtn>
      </div>
    </div>
  );
}

// Векторная схема континентов (режим «Схема», оффлайн).
function drawVectorWorld(ctx, P, dims) {
  for (const name in window.WorldGeo.LAND) {
    const poly = window.WorldGeo.LAND[name];
    ctx.beginPath();
    poly.forEach((pt, i) => { const q = P(pt[0], pt[1]); i ? ctx.lineTo(q.x, q.y) : ctx.moveTo(q.x, q.y); });
    ctx.closePath();
    ctx.fillStyle = "#173228"; ctx.fill();
    ctx.strokeStyle = "rgba(150,215,180,0.5)"; ctx.lineWidth = 0.9; ctx.stroke();
  }
}

function ZoomBtn({ children, onClick, title }) {
  return (
    <button onClick={onClick} title={title} style={{ width: 30, height: 30, borderRadius: 6, border: "1px solid var(--line)", background: "var(--bg-1)", color: "var(--ink-1)", fontSize: 16, lineHeight: 1, display: "grid", placeItems: "center" }}>{children}</button>
  );
}

Object.assign(window, { MapView });
