/* ============================================================
   VISUAL PRIMITIVES — thematic motif art, cursor, preview tile
   Motifs EVOKE each project's title (no fake screenshots).
   ============================================================ */
const { useState, useEffect, useRef } = React;

/* deterministic pseudo-random */
function rng(seed) { let s = seed; return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; }

/* ---- motif art per project theme (monochrome SVG) ---- */
function Motif({ kind }) {
  const stroke = "var(--ink)";
  let body = null;

  if (kind === "drone") {
    // radar sweep + crosshair + flight path
    body = <g>
      <g stroke={stroke} fill="none" strokeWidth="1" opacity="0.45">
        {[34, 66, 98, 130].map((r, i) => <circle key={i} cx="160" cy="120" r={r} />)}
      </g>
      <line x1="20" y1="120" x2="300" y2="120" stroke={stroke} strokeWidth="0.8" opacity="0.3" />
      <line x1="160" y1="8" x2="160" y2="232" stroke={stroke} strokeWidth="0.8" opacity="0.3" />
      <path d="M160 120 L250 56" stroke={stroke} strokeWidth="1.4" opacity="0.7" />
      <path d="M44 196 Q120 90 250 56" stroke={stroke} strokeWidth="1.2" fill="none" opacity="0.55" strokeDasharray="3 5" />
      <g transform="translate(250 56)" stroke={stroke} strokeWidth="1.6" fill="none">
        <circle r="6" /><line x1="-12" y1="0" x2="-6" y2="0" /><line x1="6" y1="0" x2="12" y2="0" /><line x1="0" y1="-12" x2="0" y2="-6" /><line x1="0" y1="6" x2="0" y2="12" />
      </g>
    </g>;
  } else if (kind === "parking") {
    // parking bays
    const bays = [];
    for (let r = 0; r < 2; r++) for (let i = 0; i < 8; i++) {
      const x = 22 + i * 35, y = r === 0 ? 36 : 150;
      bays.push(<rect key={r + "-" + i} x={x} y={y} width="28" height="54" fill="none" stroke={stroke} strokeWidth="1.1" opacity="0.55" />);
    }
    body = <g>
      {bays}
      <line x1="10" y1="120" x2="310" y2="120" stroke={stroke} strokeWidth="1.4" opacity="0.7" strokeDasharray="10 8" />
      <rect x="135" y="104" width="50" height="32" fill={stroke} opacity="0.85" rx="2" />
    </g>;
  } else if (kind === "flow") {
    // document flow / pipeline nodes
    body = <g stroke={stroke} fill="none">
      <line x1="40" y1="120" x2="280" y2="120" strokeWidth="1.2" opacity="0.5" />
      {[40, 120, 200, 280].map((x, i) => <g key={i}>
        <circle cx={x} cy="120" r="11" strokeWidth="1.4" opacity="0.8" fill={i === 3 ? stroke : "none"} />
      </g>)}
      <g opacity="0.6" strokeWidth="1.1">
        <rect x="26" y="44" width="42" height="52" rx="2" />
        <line x1="33" y1="58" x2="61" y2="58" /><line x1="33" y1="68" x2="61" y2="68" /><line x1="33" y1="78" x2="50" y2="78" />
      </g>
      <path d="M200 120 l-7 -4 m7 4 l-7 4" strokeWidth="1.4" opacity="0.8" />
    </g>;
  } else if (kind === "heat") {
    // heat-island grid + LiDAR point scatter + hotspot rings
    const r = rng(11); const cells = [];
    for (let cx = 0; cx < 8; cx++) for (let cy = 0; cy < 6; cy++) {
      const d = Math.hypot(cx - 5.5, cy - 2.2);
      const heat = Math.max(0, 1 - d / 6) * (0.5 + r() * 0.5);
      cells.push(<rect key={cx + "-" + cy} x={cx * 40} y={cy * 40} width="38" height="38" fill={stroke} opacity={(0.06 + heat * 0.5).toFixed(2)} />);
    }
    const pts = []; for (let i = 0; i < 46; i++) pts.push([r() * 320, r() * 240]);
    body = <g>
      {cells}
      {pts.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r="1.4" fill={stroke} opacity="0.5" />)}
      <g fill="none" stroke={stroke} strokeWidth="1.2" opacity="0.75">
        <circle cx="232" cy="96" r="20" /><circle cx="232" cy="96" r="34" opacity="0.55" /><circle cx="232" cy="96" r="48" opacity="0.3" />
      </g>
    </g>;
  } else if (kind === "city") {
    // buildable potential — extruded blocks
    body = <g fill={stroke}>
      <rect x="28" y="120" width="48" height="92" opacity="0.4" />
      <rect x="84" y="78" width="48" height="134" opacity="0.7" />
      <rect x="140" y="44" width="48" height="168" opacity="0.9" />
      <rect x="196" y="96" width="48" height="116" opacity="0.55" />
      <rect x="252" y="132" width="40" height="80" opacity="0.35" />
      <g stroke={stroke} strokeWidth="1" opacity="0.5" fill="none">
        <path d="M140 44 l14 -14 48 0 0 168" /><path d="M188 44 l14 -14" />
      </g>
    </g>;
  } else if (kind === "triangulate") {
    // triangulation network
    const r = rng(7); const pts = [];
    for (let i = 0; i < 7; i++) pts.push([34 + r() * 252, 30 + r() * 180]);
    const lines = [];
    for (let i = 0; i < pts.length; i++) for (let j = i + 1; j < pts.length; j++) {
      if (r() > 0.55) lines.push(<line key={i + "-" + j} x1={pts[i][0]} y1={pts[i][1]} x2={pts[j][0]} y2={pts[j][1]} />);
    }
    body = <g>
      <g stroke={stroke} strokeWidth="0.9" opacity="0.4">{lines}</g>
      {pts.map((p, i) => <circle key={i} cx={p[0]} cy={p[1]} r={i === 2 ? 6 : 3} fill={stroke} opacity={i === 2 ? 1 : 0.7} />)}
      {/* target on the located nest */}
      <circle cx={pts[2][0]} cy={pts[2][1]} r="14" fill="none" stroke={stroke} strokeWidth="1.2" opacity="0.8" />
    </g>;
  } else if (kind === "traps") {
    // optimal trap placement — coverage rings on grid
    const L = [];
    for (let i = 1; i < 8; i++) L.push(<line key={"v" + i} x1={i * 40} y1="0" x2={i * 40} y2="240" />);
    for (let i = 1; i < 6; i++) L.push(<line key={"h" + i} x1="0" y1={i * 40} x2="320" y2={i * 40} />);
    const traps = [[90, 80], [210, 70], [150, 170], [250, 165]];
    body = <g>
      <g stroke={stroke} strokeWidth="0.7" opacity="0.18">{L}</g>
      {traps.map((t, i) => <g key={i}>
        <circle cx={t[0]} cy={t[1]} r="30" fill={stroke} opacity="0.07" />
        <circle cx={t[0]} cy={t[1]} r="30" fill="none" stroke={stroke} strokeWidth="1" opacity="0.4" strokeDasharray="3 4" />
        <path d={`M${t[0]} ${t[1] - 7} l6 7 -6 7 -6 -7 z`} fill={stroke} opacity="0.85" />
      </g>)}
    </g>;
  } else if (kind === "fuel") {
    // price bars + gauge
    const hs = [110, 70, 150, 92, 130, 58, 120];
    body = <g>
      <g fill={stroke}>
        {hs.map((h, i) => <rect key={i} x={30 + i * 38} y={200 - h} width="22" height={h} opacity={h === 58 ? 1 : 0.4} />)}
      </g>
      {/* mark cheapest */}
      <path d="M239 130 l0 -18 m-5 5 l5 -6 5 6" stroke={stroke} strokeWidth="1.6" fill="none" opacity="0.9" />
      <line x1="20" y1="200" x2="300" y2="200" stroke={stroke} strokeWidth="1" opacity="0.5" />
    </g>;
  }

  return (
    <svg viewBox="0 0 320 240" preserveAspectRatio="xMidYMid slice"
      style={{ position: "absolute", inset: 0, width: "100%", height: "100%", color: "var(--ink)" }} aria-hidden="true">
      {body}
    </svg>
  );
}

/* video tile: plays muted/looped while the tab is visible OR the mouse
   hovers it; paused otherwise to save resources. No fake screenshots
   needed once a real capture exists. */
function VideoTile({ src, fallback }) {
  const ref = useRef(null);
  const hover = useRef(false);
  const [broken, setBroken] = useState(false);
  useEffect(() => {
    const v = ref.current;
    if (!v || broken) return;
    const attempt = () => { v.play().catch(() => {}); };
    const evaluate = () => { if (!document.hidden || hover.current) attempt(); else v.pause(); };
    const onEnter = () => { hover.current = true; evaluate(); };
    const onLeave = () => { hover.current = false; evaluate(); };
    v.addEventListener("mouseenter", onEnter);
    v.addEventListener("mouseleave", onLeave);
    document.addEventListener("visibilitychange", evaluate);
    const io = new IntersectionObserver((es) => es.forEach((e) => e.isIntersecting ? evaluate() : v.pause()), { threshold: 0.1 });
    io.observe(v);
    evaluate();
    return () => {
      v.removeEventListener("mouseenter", onEnter);
      v.removeEventListener("mouseleave", onLeave);
      document.removeEventListener("visibilitychange", evaluate);
      io.disconnect();
    };
  }, [src, broken]);
  if (broken) return <Motif kind={fallback} />;
  return (
    <div className="ph-video-wrap">
      <video ref={ref} src={src} muted loop playsInline preload="metadata" onError={() => setBroken(true)} />
    </div>
  );
}

/* preview tile: live iframe if `live` set, else project video, else motif art */
function PreviewTile({ project, lang, label }) {
  const L = window.I18N[lang];
  if (project && project.live) {
    return (
      <div className="ph" aria-hidden="true">
        <iframe src={project.live} title={project[lang].title} loading="lazy"
          style={{ position: "absolute", inset: 0, width: "100%", height: "100%", border: 0, background: "var(--paper)" }} />
        <div className="ph-tag">{L.live}</div>
      </div>
    );
  }
  if (project && project.video) {
    return (
      <div className="ph ph-art" aria-hidden="true">
        <VideoTile src={project.video} fallback={project.motif} />
        <div className="ph-tag">{label || L.preview}</div>
      </div>
    );
  }
  return (
    <div className="ph ph-art" aria-hidden="true">
      <Motif kind={project ? project.motif : "flow"} />
      <div className="ph-word">{project ? project[lang].word : ""}</div>
      <div className="ph-tag">{label || L.preview}</div>
    </div>
  );
}

/* ---- custom cursor ---- */
function Cursor({ style }) {
  const ref = useRef(null);
  const label = useRef(null);
  const state = useRef({ x: innerWidth / 2, y: innerHeight / 2, tx: innerWidth / 2, ty: innerHeight / 2 });

  useEffect(() => {
    if (style === "off") { document.body.classList.remove("cursor-custom"); return; }
    document.body.classList.add("cursor-custom");
    const el = ref.current;
    const move = (e) => { state.current.tx = e.clientX; state.current.ty = e.clientY; };
    const over = (e) => {
      const t = e.target.closest("[data-cursor]");
      if (t) {
        el.classList.add("hover");
        if (t.dataset.cursorSolid !== undefined) el.classList.add("solid");
        if (label.current) label.current.textContent = t.dataset.cursor || "";
      } else {
        el.classList.remove("hover", "solid");
        if (label.current) label.current.textContent = "";
      }
    };
    let raf;
    const loop = () => {
      const s = state.current;
      s.x += (s.tx - s.x) * 0.2; s.y += (s.ty - s.y) * 0.2;
      el.style.transform = `translate(${s.x}px, ${s.y}px) translate(-50%, -50%)`;
      raf = requestAnimationFrame(loop);
    };
    loop();
    window.addEventListener("mousemove", move);
    window.addEventListener("mouseover", over);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener("mousemove", move);
      window.removeEventListener("mouseover", over);
      document.body.classList.remove("cursor-custom");
    };
  }, [style]);

  if (style === "off") return null;
  return (
    <div ref={ref} className={"cursor " + (style === "ring" ? "ring" : "")}>
      <span ref={label} className="clabel"></span>
    </div>
  );
}

/* ---- national emblems for the language switch (stylized, monochrome) ---- */
function Emblem({ kind }) {
  let body = null;
  if (kind === "fr") { // coq gaulois
    body = <path d="M3 13 L7 11 L7.5 8.5 L9 9.8 L9.5 7 L11 8.6 L12 6.6 L13.2 9.2 L13.7 11.2 L15 13 L22 5.8 L19 10 L24.5 8.8 L21 13.2 L25.5 13.4 L20.5 18.5 L17 21 L17 24.5 L18.5 26 L16.2 26 L15.5 23 L14.5 23 L14 26 L11.7 26 L13.2 24.5 L13 21 L11 17.5 L9.8 14.6 L8.4 16.6 L7.6 14.2 L6 13.4 Z" />;
  } else if (kind === "en") { // crown
    body = <g><path d="M4 11l3.2 3.4L11 7l5 7 5-7 3.8 7.4L28 11l-2 13H6L4 11z" /><circle cx="4" cy="9.5" r="2" /><circle cx="28" cy="9.5" r="2" /><circle cx="16" cy="5" r="2" /><rect x="6" y="25.5" width="20" height="2.5" rx="1" /></g>;
  } else { // Luxembourg lion, crowned face (heraldic)
    body = <g>
      <circle cx="16" cy="4.2" r="2.6"/><circle cx="21.5" cy="5.6" r="2.6"/><circle cx="25.4" cy="9.8" r="2.6"/>
      <circle cx="27" cy="15.5" r="2.6"/><circle cx="25.4" cy="21.2" r="2.6"/><circle cx="21.5" cy="25.4" r="2.6"/>
      <circle cx="16" cy="26.8" r="2.6"/><circle cx="10.5" cy="25.4" r="2.6"/><circle cx="6.6" cy="21.2" r="2.6"/>
      <circle cx="5" cy="15.5" r="2.6"/><circle cx="6.6" cy="9.8" r="2.6"/><circle cx="10.5" cy="5.6" r="2.6"/>
      <circle cx="16" cy="15.5" r="8.4" fill="var(--paper, #fff)"/>
      <circle cx="16" cy="15.5" r="7.6"/>
      <circle cx="9.5" cy="9" r="2.2"/>
      <circle cx="22.5" cy="9" r="2.2"/>
      <circle cx="13" cy="14" r="1.1" fill="var(--paper, #fff)"/>
      <circle cx="19" cy="14" r="1.1" fill="var(--paper, #fff)"/>
      <path d="M13.2 18c1.2 1.4 4.4 1.4 5.6 0-.5 2.6-5.1 2.6-5.6 0z" fill="var(--paper, #fff)"/>
      <path d="M16 16.6l-1.2 1.6h2.4z" fill="var(--paper, #fff)"/>
      <path d="M12 4.6l1.4-3.2 1.2 2.3L16 1l1.4 2.7 1.2-2.3 1.4 3.2z"/>
    </g>;
  }
  return <svg className="emblem" viewBox="0 0 32 32" fill="currentColor" aria-hidden="true">{body}</svg>;
}

/* ---- slow drifting OSM backdrop for the hero (non-interactive) ---- */
function HeroMap() {
  const el = useRef(null);
  useEffect(() => {
    if (!window.L || !el.current) return;
    const L = window.L;
    // Metz → Luxembourg → Verdun → Nancy (a quadrilateral loop)
    const corners = [[49.1193, 6.1757], [49.6117, 6.1319], [49.1599, 5.3873], [48.6921, 6.1844]];
    const map = L.map(el.current, {
      zoomControl: false, attributionControl: false, dragging: false, scrollWheelZoom: false,
      doubleClickZoom: false, boxZoom: false, keyboard: false, touchZoom: false,
      fadeAnimation: false, zoomAnimation: false, inertia: false,
    });
    L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", { subdomains: "abcd", maxZoom: 19 }).addTo(map);
    const zoom = 12;
    map.setView(corners[0], zoom, { animate: false });
    setTimeout(() => map.invalidateSize(), 200);

    const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) {
      const c = [corners.reduce((s, p) => s + p[0], 0) / 4, corners.reduce((s, p) => s + p[1], 0) / 4];
      map.setView(c, 10, { animate: false });
      return () => map.remove();
    }

    const segs = []; let total = 0;
    for (let i = 0; i < corners.length; i++) {
      const a = corners[i], b = corners[(i + 1) % corners.length];
      const d = Math.hypot(b[0] - a[0], b[1] - a[1]); segs.push({ a, b, d }); total += d;
    }
    const loopMs = 150000;
    function posAt(t) {
      let dist = t * total;
      for (const s of segs) { if (dist <= s.d) { const f = dist / s.d; return [s.a[0] + (s.b[0] - s.a[0]) * f, s.a[1] + (s.b[1] - s.a[1]) * f]; } dist -= s.d; }
      return corners[0];
    }
    let raf = null, last = null, phase = 0;
    function frame(now) {
      if (last == null) last = now;
      phase += now - last; last = now;
      const t = (phase % loopMs) / loopMs;
      map.setView(posAt(t), zoom, { animate: false });
      raf = requestAnimationFrame(frame);
    }
    function start() { if (!raf) { last = null; raf = requestAnimationFrame(frame); } }
    function stop() { if (raf) { cancelAnimationFrame(raf); raf = null; } }
    start();
    const io = new IntersectionObserver((es) => { es.forEach((e) => e.isIntersecting ? start() : stop()); }, { threshold: 0 });
    io.observe(el.current);
    const onVis = () => document.hidden ? stop() : start();
    document.addEventListener("visibilitychange", onVis);
    return () => { stop(); io.disconnect(); document.removeEventListener("visibilitychange", onVis); map.remove(); };
  }, []);
  return <div className="hero-map" ref={el} aria-hidden="true"></div>;
}

/* ---- right-edge scroll progress bar ---- */
function ScrollProgress() {
  const fillRef = useRef(null);
  useEffect(() => {
    const onScroll = () => {
      const h = document.documentElement;
      const max = h.scrollHeight - h.clientHeight;
      const pct = max > 0 ? Math.min(100, Math.max(0, (h.scrollTop / max) * 100)) : 0;
      if (fillRef.current) fillRef.current.style.height = pct + "%";
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, []);
  return (
    <div className="scroll-progress" aria-hidden="true">
      <div className="scroll-progress-fill" ref={fillRef}></div>
    </div>
  );
}

Object.assign(window, { Motif, PreviewTile, Cursor, Emblem, HeroMap, ScrollProgress });
