// src/effects.jsx — interaction & motion enhancements
// All effects are restrained: small displacement, soft easing, no flashing.

// ── 1. Top scroll progress bar ─────────────────────────────────────
function ScrollProgress() {
  const ref = React.useRef(null);
  React.useEffect(() => {
    let frame = null;
    const update = () => {
      const h = document.documentElement;
      const scrolled = h.scrollTop || document.body.scrollTop;
      const max = (h.scrollHeight - h.clientHeight) || 1;
      const pct = Math.max(0, Math.min(1, scrolled / max));
      if (ref.current) ref.current.style.transform = `scaleX(${pct})`;
    };
    const onScroll = () => {
      if (frame) cancelAnimationFrame(frame);
      frame = requestAnimationFrame(update);
    };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    update();
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, []);
  return <div className="scroll-progress" ref={ref} />;
}

// ── 2. Cursor ink dot (desktop only) ───────────────────────────────
function CursorInk() {
  const dotRef = React.useRef(null);
  const trailRef = React.useRef(null);
  const target = React.useRef({ x: -50, y: -50 });
  const pos = React.useRef({ x: -50, y: -50 });
  const trailPos = React.useRef({ x: -50, y: -50 });
  const [visible, setVisible] = React.useState(false);
  const [linkHover, setLinkHover] = React.useState(false);

  React.useEffect(() => {
    // Skip on coarse pointers (touch) — checked at mount
    if (window.matchMedia("(pointer: coarse)").matches) return undefined;
    setVisible(true);

    const onMove = (e) => {
      target.current.x = e.clientX;
      target.current.y = e.clientY;
      // Detect interactive hovers
      const el = e.target;
      const isHot = el && (el.closest("a, button, .essay-mini, .essay-row, .insight, .work-card, .svc-tier, .svc-card, .tier, .value-row, .tl-node, .mm-node, .pill, .radio, .think-tabs button"));
      setLinkHover(!!isHot);
    };
    const onLeave = () => { target.current.x = -50; target.current.y = -50; };

    window.addEventListener("mousemove", onMove);
    document.documentElement.addEventListener("mouseleave", onLeave);

    let raf = null;
    const tick = () => {
      // Smooth follow
      pos.current.x += (target.current.x - pos.current.x) * 0.35;
      pos.current.y += (target.current.y - pos.current.y) * 0.35;
      trailPos.current.x += (target.current.x - trailPos.current.x) * 0.12;
      trailPos.current.y += (target.current.y - trailPos.current.y) * 0.12;
      if (dotRef.current) {
        dotRef.current.style.transform = `translate(${pos.current.x}px, ${pos.current.y}px)`;
      }
      if (trailRef.current) {
        trailRef.current.style.transform = `translate(${trailPos.current.x}px, ${trailPos.current.y}px)`;
      }
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      window.removeEventListener("mousemove", onMove);
      document.documentElement.removeEventListener("mouseleave", onLeave);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  if (!visible) return null;
  return (
    <>
      <div ref={trailRef} className={`cursor-trail${linkHover ? " hot" : ""}`} aria-hidden="true" />
      <div ref={dotRef} className={`cursor-dot${linkHover ? " hot" : ""}`} aria-hidden="true" />
    </>
  );
}

// ── 3. Mouse tilt wrapper ──────────────────────────────────────────
// Wrap children with subtle 3D tilt on hover.
function useTilt(maxDeg = 5) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return undefined;
    if (window.matchMedia("(pointer: coarse)").matches) return undefined;
    let raf = null;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      const cx = r.left + r.width / 2;
      const cy = r.top + r.height / 2;
      const dx = (e.clientX - cx) / (r.width / 2);
      const dy = (e.clientY - cy) / (r.height / 2);
      if (raf) cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        el.style.transform = `perspective(800px) rotateY(${dx * maxDeg}deg) rotateX(${-dy * maxDeg}deg) translateY(-2px)`;
      });
    };
    const onLeave = () => {
      el.style.transform = "";
    };
    el.addEventListener("mousemove", onMove);
    el.addEventListener("mouseleave", onLeave);
    return () => {
      el.removeEventListener("mousemove", onMove);
      el.removeEventListener("mouseleave", onLeave);
      if (raf) cancelAnimationFrame(raf);
    };
  }, [maxDeg]);
  return ref;
}

// Auto-apply tilt to all .work-card elements on the page
function AutoTilt({ selector, maxDeg = 5 }) {
  React.useEffect(() => {
    if (window.matchMedia("(pointer: coarse)").matches) return undefined;
    const attached = new WeakSet();
    const attach = () => {
      document.querySelectorAll(selector).forEach((el) => {
        if (attached.has(el)) return;
        attached.add(el);
        let raf = null;
        const onMove = (e) => {
          const r = el.getBoundingClientRect();
          const cx = r.left + r.width / 2;
          const cy = r.top + r.height / 2;
          const dx = (e.clientX - cx) / (r.width / 2);
          const dy = (e.clientY - cy) / (r.height / 2);
          if (raf) cancelAnimationFrame(raf);
          raf = requestAnimationFrame(() => {
            el.style.transform = `perspective(800px) rotateY(${dx * maxDeg}deg) rotateX(${-dy * maxDeg}deg) translateY(-3px)`;
          });
        };
        const onLeave = () => {
          if (raf) cancelAnimationFrame(raf);
          el.style.transform = "";
        };
        el.addEventListener("mousemove", onMove);
        el.addEventListener("mouseleave", onLeave);
      });
    };
    // Initial + observe new additions
    attach();
    const mo = new MutationObserver(attach);
    mo.observe(document.body, { childList: true, subtree: true });
    return () => mo.disconnect();
  }, [selector, maxDeg]);
  return null;
}

// ── 4. Magnetic buttons ────────────────────────────────────────────
function MagneticButtons({ selector = ".btn-primary, .nav .cta, .smart-cta" }) {
  React.useEffect(() => {
    if (window.matchMedia("(pointer: coarse)").matches) return undefined;
    const attached = new WeakSet();
    const attach = () => {
      document.querySelectorAll(selector).forEach((el) => {
        if (attached.has(el)) return;
        attached.add(el);
        let raf = null;
        const onMove = (e) => {
          const r = el.getBoundingClientRect();
          const cx = r.left + r.width / 2;
          const cy = r.top + r.height / 2;
          // distance from center as fraction of half-width
          const dx = e.clientX - cx;
          const dy = e.clientY - cy;
          const dist = Math.hypot(dx, dy);
          const range = Math.max(r.width, r.height);
          if (dist > range) {
            el.style.transform = "";
            return;
          }
          const pull = 0.25; // 25% of distance
          if (raf) cancelAnimationFrame(raf);
          raf = requestAnimationFrame(() => {
            el.style.transform = `translate(${dx * pull}px, ${dy * pull}px)`;
          });
        };
        const onLeave = () => {
          if (raf) cancelAnimationFrame(raf);
          el.style.transition = "transform .4s cubic-bezier(.3,.7,.2,1)";
          el.style.transform = "";
          setTimeout(() => { el.style.transition = ""; }, 400);
        };
        const onEnter = () => {
          el.style.transition = "transform .15s ease-out";
        };
        // Listen on parent area for ranged effect
        const parent = el.parentElement;
        parent.addEventListener("mousemove", onMove);
        el.addEventListener("mouseenter", onEnter);
        parent.addEventListener("mouseleave", onLeave);
      });
    };
    attach();
    const mo = new MutationObserver(attach);
    mo.observe(document.body, { childList: true, subtree: true });
    return () => mo.disconnect();
  }, [selector]);
  return null;
}

// ── 5. Theme switch radial reveal ──────────────────────────────────
// When data-theme changes on <html>, play a radial wipe from a stored origin.
function ThemeReveal() {
  React.useEffect(() => {
    let timeout = null;
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((m) => {
        if (m.attributeName !== "data-theme") return;
        // Origin = last click position, defaults to bottom-right of viewport
        const o = window.__lastClick || { x: window.innerWidth - 48, y: window.innerHeight - 48 };
        const overlay = document.createElement("div");
        overlay.className = "theme-reveal";
        overlay.style.setProperty("--x", o.x + "px");
        overlay.style.setProperty("--y", o.y + "px");
        document.body.appendChild(overlay);
        // Force layout
        // eslint-disable-next-line no-unused-expressions
        overlay.offsetWidth;
        overlay.classList.add("on");
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(() => { overlay.remove(); }, 900);
      });
    });
    observer.observe(document.documentElement, { attributes: true });

    const onClick = (e) => {
      window.__lastClick = { x: e.clientX, y: e.clientY };
    };
    window.addEventListener("click", onClick, true);

    return () => { observer.disconnect(); window.removeEventListener("click", onClick, true); if (timeout) clearTimeout(timeout); };
  }, []);
  return null;
}

// ── 6. Mental-Model edge stroke-draw on active change ─────────────
// Watches .mm-svg paths and triggers a dash-offset draw whenever
// the .active class is added or the SVG is rebuilt.
function MentalModelDraw() {
  React.useEffect(() => {
    let raf = null;
    const apply = () => {
      const svgs = document.querySelectorAll(".mm-svg path");
      svgs.forEach((p) => {
        if (!p.dataset.lenInit) {
          const len = p.getTotalLength ? p.getTotalLength() : 200;
          p.style.strokeDasharray = len;
          p.style.strokeDashoffset = "0";
          p.dataset.lenInit = "1";
          p.dataset.len = len;
        }
      });
    };
    const draw = (path) => {
      const len = parseFloat(path.dataset.len || 200);
      path.style.transition = "none";
      path.style.strokeDashoffset = len;
      // Reflow
      // eslint-disable-next-line no-unused-expressions
      path.getBoundingClientRect();
      path.style.transition = "stroke-dashoffset .7s cubic-bezier(.3,.8,.2,1), stroke .25s";
      path.style.strokeDashoffset = "0";
    };
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((m) => {
        if (m.type === "attributes" && m.attributeName === "class" && m.target.tagName === "path") {
          if (m.target.classList.contains("active")) draw(m.target);
        }
        if (m.type === "childList") apply();
      });
    });
    const start = () => {
      apply();
      const svgs = document.querySelectorAll(".mm-svg");
      svgs.forEach((s) => observer.observe(s, { childList: true, subtree: true, attributes: true, attributeFilter: ["class"] }));
    };
    // Wait a tick for React to mount
    raf = requestAnimationFrame(start);
    // Re-check on Thinking tab change
    const tabsObserver = new MutationObserver(start);
    tabsObserver.observe(document.body, { childList: true, subtree: true });
    return () => { observer.disconnect(); tabsObserver.disconnect(); if (raf) cancelAnimationFrame(raf); };
  }, []);
  return null;
}

// ── Bundle root ───────────────────────────────────────────────────
function Effects() {
  return (
    <>
      <ScrollProgress />
      <CursorInk />
      <AutoTilt selector=".work-card" maxDeg={4} />
      <AutoTilt selector=".svc-card" maxDeg={3} />
      <MagneticButtons />
      <ThemeReveal />
      <MentalModelDraw />
    </>
  );
}

Object.assign(window, {
  ScrollProgress, CursorInk, AutoTilt, MagneticButtons,
  ThemeReveal, MentalModelDraw, Effects,
});
