// MagnetLines — ported from React Bits (reactbits.dev) to plain JSX. A grid of short strokes
// that all point at the cursor. It fills the empty bottom-left of the home FAQ block.
//
// Deviations from upstream, all deliberate:
//
//   * It only tracks the pointer while it is on screen near the grid. Upstream listens to
//     every `pointermove` on the window for the life of the page and, on each one, calls
//     getBoundingClientRect on all 81 strokes — 81 forced reflows per mouse move, for a
//     decoration in a corner. Stroke centres are measured once and cached instead, and the
//     listener only runs while the grid is in view.
//   * Sizes come from the caller in px, not `vmin`. Upstream's 80vmin default is sized to be
//     the whole page; this sits inside one column of a grid and has to obey it.
//   * It renders nothing under prefers-reduced-motion or for a coarse pointer. There is no
//     pointer to follow on a phone, and it would just be 81 static dashes.
//   * The base angle is the resting state, and the strokes return to it when the pointer
//     leaves, rather than freezing wherever they last pointed.
const MagnetLines = ({
  rows,
  columns,
  cell = 30,
  size,
  lineColor = "currentColor",
  lineWidth = 2,
  lineHeight = 14,
  baseAngle = -10,
  className = "",
  style = {},
}) => {
  const { useRef, useEffect, useState } = React;
  const containerRef = useRef(null);

  // Upstream takes a fixed `containerSize` for both axes. This fills whatever box it is given
  // instead, and derives the grid from the measured area so the cells stay roughly square at
  // any shape — a fixed 9x9 stretched into a wide box gives a row of lonely dashes. `rows` /
  // `columns` still override if a caller wants an exact grid.
  const [grid, setGrid] = useState({ c: columns || 9, r: rows || 9 });
  useEffect(() => {
    const el = containerRef.current;
    if (!el || (rows && columns)) return;
    const measure = () => {
      const b = el.getBoundingClientRect();
      if (!b.width || !b.height) return;
      const c = columns || Math.max(3, Math.round(b.width / cell));
      const r = rows || Math.max(3, Math.round(b.height / cell));
      setGrid((prev) => (prev.c === c && prev.r === r ? prev : { c, r }));
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, [rows, columns, cell]);

  const cols = columns || grid.c;
  const rowCount = rows || grid.r;

  const reduced = typeof window !== "undefined" && window.matchMedia
    && (window.matchMedia("(prefers-reduced-motion: reduce)").matches
     || window.matchMedia("(pointer: coarse)").matches);

  useEffect(() => {
    if (reduced) return;
    const container = containerRef.current;
    if (!container) return;
    const items = Array.from(container.querySelectorAll("span"));
    if (!items.length) return;

    let centers = [];
    const measure = () => {
      centers = items.map((el) => {
        const r = el.getBoundingClientRect();
        return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
      });
    };

    let raf = 0;
    const pointer = { x: 0, y: 0 };
    const paint = () => {
      raf = 0;
      for (let i = 0; i < items.length; i++) {
        const c = centers[i];
        if (!c) continue;
        const b = pointer.x - c.x;
        const a = pointer.y - c.y;
        const len = Math.sqrt(a * a + b * b) || 1;
        const deg = ((Math.acos(b / len) * 180) / Math.PI) * (pointer.y > c.y ? 1 : -1);
        items[i].style.setProperty("--rotate", `${deg}deg`);
      }
    };
    const onMove = (e) => {
      pointer.x = e.clientX;
      pointer.y = e.clientY;
      if (!raf) raf = requestAnimationFrame(paint);
    };
    const rest = () => {
      items.forEach((el) => el.style.setProperty("--rotate", `${baseAngle}deg`));
    };

    // Centres are viewport-relative, so they move with the page: re-measure on scroll and
    // resize rather than per stroke per frame.
    let listening = false;
    const listen = (on) => {
      if (on === listening) return;
      listening = on;
      if (on) { measure(); window.addEventListener("pointermove", onMove, { passive: true }); }
      else { window.removeEventListener("pointermove", onMove); if (raf) { cancelAnimationFrame(raf); raf = 0; } rest(); }
    };

    const io = typeof IntersectionObserver === "function"
      ? new IntersectionObserver((es) => listen(es.some((e) => e.isIntersecting)), { rootMargin: "120px" })
      : null;
    if (io) io.observe(container); else listen(true);

    const onScroll = () => { if (listening) measure(); };
    window.addEventListener("scroll", onScroll, { passive: true });
    const ro = new ResizeObserver(measure);
    ro.observe(container);

    return () => {
      if (io) io.disconnect();
      ro.disconnect();
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("pointermove", onMove);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [rowCount, cols, baseAngle, reduced]);

  if (reduced) return null;

  return (
    <div ref={containerRef}
         className={`magnet-lines ${className}`.trim()}
         aria-hidden="true"
         style={{
           gridTemplateColumns: `repeat(${cols}, 1fr)`,
           gridTemplateRows: `repeat(${rowCount}, 1fr)`,
           width: size || "100%", height: size || "100%", color: lineColor, ...style,
         }}>
      {Array.from({ length: rowCount * cols }, (_, i) => (
        <span key={i} style={{ "--rotate": `${baseAngle}deg`, width: lineWidth, height: lineHeight }} />
      ))}
    </div>
  );
};
window.MagnetLines = MagnetLines;
