// ============================================================
// Beyond the Falls - shared library
// hooks · glyphs · cursor reactivity · modal shell
// ============================================================
const { useEffect, useRef, useState, useCallback, useLayoutEffect } = React;

// -------- IntersectionObserver reveal helper ----------------
function useReveal() {
  useEffect(() => {
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.add('in');
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.12, rootMargin: '0px 0px -6% 0px' });
    const scan = () => document.querySelectorAll('.reveal:not(.in)').forEach(el => io.observe(el));
    scan();
    // rescan when DOM grows (modals etc.)
    const mo = new MutationObserver(scan);
    mo.observe(document.body, { childList: true, subtree: true });
    // safety net: if anything stalls, never leave content trapped invisible
    const fallback = setTimeout(() => document.documentElement.classList.add('reveal-fallback'), 5000);
    return () => { io.disconnect(); mo.disconnect(); clearTimeout(fallback); };
  }, []);
}

// -------- Magnetic element (pulls toward cursor) ------------
function Magnetic({ children, strength = 0.35, className = '', as = 'div', ...rest }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    let raf = 0, tx = 0, ty = 0, cx = 0, cy = 0;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      tx = (e.clientX - (r.left + r.width / 2)) * strength;
      ty = (e.clientY - (r.top + r.height / 2)) * strength;
    };
    const onLeave = () => { tx = 0; ty = 0; };
    const tick = () => {
      cx += (tx - cx) * 0.15; cy += (ty - cy) * 0.15;
      el.style.transform = `translate(${cx.toFixed(2)}px, ${cy.toFixed(2)}px)`;
      raf = requestAnimationFrame(tick);
    };
    el.addEventListener('mousemove', onMove);
    el.addEventListener('mouseleave', onLeave);
    raf = requestAnimationFrame(tick);
    return () => { el.removeEventListener('mousemove', onMove); el.removeEventListener('mouseleave', onLeave); cancelAnimationFrame(raf); };
  }, [strength]);
  const Tag = as;
  return <Tag ref={ref} className={className} style={{ willChange: 'transform' }} {...rest}>{children}</Tag>;
}

// -------- Tilt card (3D rotate + glare toward cursor) -------
function Tilt({ children, max = 10, glare = true, className = '', style = {}, ...rest }) {
  const ref = useRef(null);
  const glareRef = useRef(null);
  const onMove = useCallback((e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width;
    const py = (e.clientY - r.top) / r.height;
    const rotY = (px - 0.5) * max * 2;
    const rotX = -(py - 0.5) * max * 2;
    el.style.transform = `perspective(900px) rotateX(${rotX.toFixed(2)}deg) rotateY(${rotY.toFixed(2)}deg) translateZ(0)`;
    if (glareRef.current) {
      glareRef.current.style.opacity = '1';
      glareRef.current.style.background =
        `radial-gradient(420px circle at ${px * 100}% ${py * 100}%, rgba(255,255,255,.18), transparent 45%)`;
    }
  }, [max]);
  const onLeave = useCallback(() => {
    const el = ref.current; if (!el) return;
    el.style.transform = 'perspective(900px) rotateX(0) rotateY(0)';
    if (glareRef.current) glareRef.current.style.opacity = '0';
  }, []);
  return (
    <div ref={ref} onMouseMove={onMove} onMouseLeave={onLeave}
         className={className}
         style={{ transition: 'transform .25s cubic-bezier(.2,.7,.2,1)', transformStyle: 'preserve-3d', ...style }} {...rest}>
      {children}
      {glare && <div ref={glareRef} className="pointer-events-none absolute inset-0 rounded-[inherit]" style={{ opacity: 0, transition: 'opacity .3s' }}/>}
    </div>
  );
}

// -------- Parallax on scroll (translateY by factor) --------
function useParallax(factor = 0.12) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    let raf = 0;
    const update = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const mid = r.top + r.height / 2;
      const off = (mid - vh / 2) / vh; // -1..1
      el.style.transform = `translate3d(0, ${(-off * factor * 100).toFixed(2)}px, 0)`;
      raf = 0;
    };
    const onScroll = () => { if (!raf) raf = requestAnimationFrame(update); };
    window.addEventListener('scroll', onScroll, { passive: true });
    update();
    return () => { window.removeEventListener('scroll', onScroll); if (raf) cancelAnimationFrame(raf); };
  }, [factor]);
  return ref;
}

// -------- Spotlight that follows cursor inside a section ----
function useSpotlight(color = 'rgba(124,255,203,.10)', size = 600) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const onMove = (e) => {
      const r = el.getBoundingClientRect();
      el.style.setProperty('--mx', `${e.clientX - r.left}px`);
      el.style.setProperty('--my', `${e.clientY - r.top}px`);
      el.style.setProperty('--spot-o', '1');
    };
    const onLeave = () => el.style.setProperty('--spot-o', '0');
    el.addEventListener('mousemove', onMove);
    el.addEventListener('mouseleave', onLeave);
    el.style.setProperty('--spot-c', color);
    el.style.setProperty('--spot-s', size + 'px');
    return () => { el.removeEventListener('mousemove', onMove); el.removeEventListener('mouseleave', onLeave); };
  }, [color, size]);
  return ref;
}

// -------- Global soft cursor glow + ring follower -----------
function CursorGlow() {
  const glowRef = useRef(null);
  const ringRef = useRef(null);
  useEffect(() => {
    if (window.matchMedia('(pointer: coarse)').matches) return;
    let raf = 0, mx = innerWidth/2, my = innerHeight/2, rx = mx, ry = my;
    const onMove = (e) => { mx = e.clientX; my = e.clientY;
      if (glowRef.current) glowRef.current.style.transform = `translate(${mx}px, ${my}px)`;
    };
    const tick = () => {
      rx += (mx - rx) * 0.18; ry += (my - ry) * 0.18;
      if (ringRef.current) ringRef.current.style.transform = `translate(${rx}px, ${ry}px)`;
      raf = requestAnimationFrame(tick);
    };
    const onOver = (e) => {
      const a = e.target.closest('a,button,[data-magnet]');
      if (ringRef.current) ringRef.current.style.setProperty('--ring-s', a ? '2.2' : '1');
    };
    window.addEventListener('mousemove', onMove, { passive: true });
    window.addEventListener('mouseover', onOver, { passive: true });
    raf = requestAnimationFrame(tick);
    return () => { window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseover', onOver); cancelAnimationFrame(raf); };
  }, []);
  return (
    <>
      <div ref={glowRef} className="cursor-glow"/>
      <div ref={ringRef} className="cursor-ring"/>
    </>
  );
}

// -------- Animated counter (counts up when in view) --------
function Counter({ to, suffix = '', prefix = '', dur = 1400 }) {
  const ref = useRef(null);
  const [val, setVal] = useState(0);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    let started = false;
    const io = new IntersectionObserver((es) => {
      es.forEach(e => {
        if (e.isIntersecting && !started) {
          started = true;
          const t0 = performance.now();
          const step = (t) => {
            const p = Math.min(1, (t - t0) / dur);
            const eased = 1 - Math.pow(1 - p, 3);
            setVal(Math.round(eased * to));
            if (p < 1) requestAnimationFrame(step);
          };
          requestAnimationFrame(step);
          io.unobserve(el);
        }
      });
    }, { threshold: 0.5 });
    io.observe(el);
    return () => io.disconnect();
  }, [to, dur]);
  return <span ref={ref}>{prefix}{val}{suffix}</span>;
}

// -------- Tiny icon glyphs ----------------------------------
const Glyph = {
  arrow: (p) => (<svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M5 12h14M13 6l6 6-6 6"/></svg>),
  star:  (p) => (<svg {...p} viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l2.9 6.9 7.4.6-5.6 4.9 1.7 7.3L12 17.8 5.6 21.7l1.7-7.3L1.7 9.5l7.4-.6L12 2z"/></svg>),
  close: (p) => (<svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M6 6l12 12M18 6L6 18"/></svg>),
  plus:  (p) => (<svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M12 5v14M5 12h14"/></svg>),
  dot:   (p) => (<svg {...p} viewBox="0 0 8 8"><circle cx="4" cy="4" r="2.5" fill="currentColor"/></svg>),
  drop:  (p) => (<svg {...p} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M12 3s6 6.5 6 11a6 6 0 1 1-12 0C6 9.5 12 3 12 3z"/></svg>),
  spark: (p) => (<svg {...p} viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6L12 2z"/></svg>),
};

// -------- Reusable blur Modal shell -------------------------
// Props: onClose, accent, bg, fg, tag, title, script, children, file
function Modal({ onClose, accent = '#7CFFCB', bg = '#062016', fg = '#F4F3EE', tag, title, script, children, file }) {
  useEffect(() => {
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => { document.body.style.overflow = prev; window.removeEventListener('keydown', onKey); };
  }, [onClose]);

  return (
    <div className="fixed inset-0 z-[80] modal-backdrop backdrop-enter flex items-start md:items-center justify-center p-4 md:p-6 overflow-y-auto" onClick={onClose}>
      <div className="modal-enter w-full max-w-4xl my-auto rounded-3xl overflow-hidden relative"
           onClick={(e) => e.stopPropagation()}
           style={{ background: bg, color: fg, border: `1px solid ${fg}20` }}>
        <div className="absolute inset-0 plate-stripes-light opacity-20 pointer-events-none"/>
        <div className="absolute -top-24 -right-24 w-72 h-72 rounded-full pointer-events-none blur-3xl" style={{ background: `${accent}22` }}/>
        <Magnetic strength={0.5} as="button" onClick={onClose}
                className="absolute top-5 right-5 z-10 w-11 h-11 rounded-full flex items-center justify-center border"
                style={{ borderColor: `${fg}30`, background: `${fg}12`, color: fg }} data-magnet>
          <Glyph.close className="w-4 h-4"/>
        </Magnetic>
        <div className="relative p-7 md:p-12">
          {tag && (
            <div className="flex items-center gap-2 text-[10px] font-mono tracking-[0.22em] uppercase" style={{ color: `${fg}aa` }}>
              <Glyph.dot className="w-2 h-2" style={{ color: accent }}/> {tag}
            </div>
          )}
          {title && (
            <h3 className="mt-3 font-display font-bold tracking-tight" style={{ fontSize: 'clamp(34px, 5vw, 72px)', lineHeight: .96, letterSpacing: '-0.03em', textWrap: 'balance' }}>
              {title}
            </h3>
          )}
          {script && (
            <div className="mt-1 script-text" style={{ color: accent, fontSize: 'clamp(26px, 3vw, 44px)', transform: 'rotate(-2deg)', display: 'inline-block' }}>
              {script}
            </div>
          )}
          <div className="mt-6">{children}</div>
          <div className="mt-10 flex items-center justify-between text-[10px] font-mono tracking-[0.2em] uppercase" style={{ color: `${fg}80` }}>
            <span>{file || 'Beyond the Falls'}</span>
            <span>Esc to close</span>
          </div>
        </div>
      </div>
    </div>
  );
}

// -------- Pill ----------------------------------------------
function Pill({ children, fg = '#F4F3EE', accent = '#7CFFCB', solid = false }) {
  return (
    <span className="pill" style={solid
      ? { background: accent, color: '#0A0A0A' }
      : { background: `${fg}12`, color: fg, border: `1px solid ${fg}2e` }}>
      <Glyph.dot className="w-1.5 h-1.5" style={{ color: solid ? '#0A0A0A' : accent }}/> {children}
    </span>
  );
}

Object.assign(window, {
  useReveal, Magnetic, Tilt, useParallax, useSpotlight, CursorGlow, Counter, Glyph, Modal, Pill,
});
