// Hyper · custom cursor + scroll utils + reveal observer
const { useEffect, useRef, useState } = React;

// ── Preview mode: when the site is loaded inside the phone-frame previewer
//    (?preview=1) we suppress the desktop-only custom cursor and restore the
//    native pointer, so the page feels like a real touch device.
const HYPER_PREVIEW = (() => {
  try { return new URLSearchParams(window.location.search).get('preview') === '1'; }
  catch (e) { return false; }
})();
if (HYPER_PREVIEW) document.documentElement.classList.add('is-preview');

function useMousePos() {
  const ref = useRef({ x: -100, y: -100 });
  useEffect(() => {
    const onMove = (e) => { ref.current.x = e.clientX; ref.current.y = e.clientY; };
    window.addEventListener('mousemove', onMove);
    return () => window.removeEventListener('mousemove', onMove);
  }, []);
  return ref;
}

function CustomCursor() {
  if (HYPER_PREVIEW) return null;
  const dotRef = useRef(null);
  const ringRef = useRef(null);
  const labelRef = useRef(null);
  const state = useRef({
    tx: window.innerWidth/2, ty: window.innerHeight/2,
    dx: 0, dy: 0, rx: 0, ry: 0,
    label: '', hover: false, text: false
  });

  useEffect(() => {
    const onMove = (e) => { state.current.tx = e.clientX; state.current.ty = e.clientY; };
    window.addEventListener('mousemove', onMove);

    // hover detection
    const setHover = (h, l = '', mode = 'default') => {
      state.current.hover = h;
      state.current.label = l;
      if (ringRef.current) {
        ringRef.current.classList.toggle('is-hover', h && mode === 'default');
        ringRef.current.classList.toggle('is-text', h && mode === 'text');
        ringRef.current.classList.toggle('is-xray', h && mode === 'xray');
      }
      if (labelRef.current) {
        labelRef.current.textContent = l;
        labelRef.current.classList.toggle('visible', !!l);
      }
    };

    const over = (e) => {
      const t = e.target.closest('[data-cursor]');
      if (t) {
        const kind = t.getAttribute('data-cursor');
        const label = t.getAttribute('data-cursor-label') || '';
        if (kind === 'text') setHover(true, label, 'text');
        else if (kind === 'xray') setHover(true, label, 'xray');
        else setHover(true, label, 'default');
      } else if (e.target.closest('a, button, input, textarea')) {
        setHover(true, '', 'default');
      } else {
        setHover(false, '', 'default');
      }
    };
    document.addEventListener('mouseover', over);

    let raf;
    const tick = () => {
      const s = state.current;
      // dot — follows immediately
      s.dx += (s.tx - s.dx) * 0.6;
      s.dy += (s.ty - s.dy) * 0.6;
      // ring — slower lerp
      s.rx += (s.tx - s.rx) * 0.18;
      s.ry += (s.ty - s.ry) * 0.18;
      if (dotRef.current) dotRef.current.style.transform = `translate3d(${s.dx}px, ${s.dy}px, 0) translate(-50%,-50%)`;
      if (ringRef.current) ringRef.current.style.transform = `translate3d(${s.rx}px, ${s.ry}px, 0) translate(-50%,-50%)`;
      if (labelRef.current) labelRef.current.style.transform = `translate3d(${s.tx + 18}px, ${s.ty + 18}px, 0) scale(${s.label ? 1 : 0})`;
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      window.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseover', over);
      cancelAnimationFrame(raf);
    };
  }, []);

  return (
    <>
      <div className="cursor-dot" ref={dotRef}></div>
      <div className="cursor-ring" ref={ringRef}></div>
      <div className="cursor-label" ref={labelRef}></div>
    </>
  );
}

// Apply 'in' class to .fade-up when in view
function useReveal() {
  useEffect(() => {
    const els = document.querySelectorAll('.fade-up, .metric');
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.add('in');
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.18 });
    els.forEach(el => io.observe(el));
    return () => io.disconnect();
  }, []);
}

// Magnetic effect — element follows cursor slightly when nearby
function useMagnetic() {
  useEffect(() => {
    const els = document.querySelectorAll('.magnetic');
    const handlers = [];
    els.forEach(el => {
      const enter = () => {};
      const move = (e) => {
        const r = el.getBoundingClientRect();
        const cx = r.left + r.width / 2;
        const cy = r.top + r.height / 2;
        const dx = (e.clientX - cx) * 0.25;
        const dy = (e.clientY - cy) * 0.35;
        el.style.transform = `translate(${dx}px, ${dy}px)`;
      };
      const leave = () => { el.style.transform = ''; };
      el.addEventListener('mouseenter', enter);
      el.addEventListener('mousemove', move);
      el.addEventListener('mouseleave', leave);
      handlers.push([el, enter, move, leave]);
    });
    return () => handlers.forEach(([el, e, m, l]) => {
      el.removeEventListener('mouseenter', e);
      el.removeEventListener('mousemove', m);
      el.removeEventListener('mouseleave', l);
    });
  }, []);
}

// Parallax via data-parallax="0.2"
function useParallax() {
  useEffect(() => {
    const els = [...document.querySelectorAll('[data-parallax]')].map(el => ({
      el,
      f: parseFloat(el.getAttribute('data-parallax') || '0.1')
    }));
    let raf;
    const tick = () => {
      const y = window.scrollY;
      els.forEach(({ el, f }) => {
        el.style.transform = `translate3d(0, ${y * f * -1}px, 0)`;
      });
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, []);
}

// Glow that follows cursor within container [data-glow]
function useFollowGlow() {
  useEffect(() => {
    const els = document.querySelectorAll('[data-glow]');
    const onMove = (e) => {
      const el = e.currentTarget;
      const inner = el.querySelector('.glow');
      if (!inner) return;
      const r = el.getBoundingClientRect();
      inner.style.left = (e.clientX - r.left - 200) + 'px';
      inner.style.top  = (e.clientY - r.top  - 200) + 'px';
    };
    els.forEach(el => el.addEventListener('mousemove', onMove));
    return () => els.forEach(el => el.removeEventListener('mousemove', onMove));
  }, []);
}

// ── 3D eye-tracking: hero brand mark follows the cursor like a pair of eyes ──
function useEyesFollowCursor() {
  useEffect(() => {
    const stage = document.querySelector('.hero-mark-stage');
    if (!stage) return;

    const tgt = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    const cur = { x: tgt.x, y: tgt.y };
    let bob = 0; // gentle idle float so it never feels frozen

    const onMove = (e) => { tgt.x = e.clientX; tgt.y = e.clientY; };
    window.addEventListener('mousemove', onMove);

    let raf;
    const tick = () => {
      cur.x += (tgt.x - cur.x) * 0.08;
      cur.y += (tgt.y - cur.y) * 0.08;
      bob += 0.02;

      const r = stage.getBoundingClientRect();
      const cx = r.left + r.width / 2;
      const cy = r.top + r.height / 2;
      // Normalize to roughly [-1, 1] within a comfortable radius
      const radius = Math.max(window.innerWidth, window.innerHeight) * 0.45;
      const nx = Math.max(-1, Math.min(1, (cur.x - cx) / radius));
      const ny = Math.max(-1, Math.min(1, (cur.y - cy) / radius));

      const rotY = nx * 22;          // left/right head turn
      const rotX = -ny * 14;          // up/down tilt
      const tX   = nx * 14;           // small parallax shift
      const tY   = ny * 8 + Math.sin(bob) * 4; // shift + idle bob

      stage.style.transform =
        `perspective(900px) rotateY(${rotY.toFixed(2)}deg) rotateX(${rotX.toFixed(2)}deg) translate3d(${tX.toFixed(2)}px, ${tY.toFixed(2)}px, 0)`;

      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);

    return () => {
      window.removeEventListener('mousemove', onMove);
      cancelAnimationFrame(raf);
      stage.style.transform = '';
    };
  }, []);
}

// ── X-ray scanner: punch a circular hole at the cursor and fill it with scrambled digits
//    so it looks like the lens scans the skeleton of letters / brand mark.
function useXrayScanner() {
  useEffect(() => {
    const DIGITS = '0123456789';
    const targets = Array.from(document.querySelectorAll('[data-cursor="xray"]'));
    const cleanups = [];

    const randDigit = () => DIGITS[(Math.random() * 10) | 0];

    // Short marketing terms that occasionally surface inside the letterforms
    // alongside the digits (ROI, CTR, …). They sit in place and get slowly
    // eroded back into digits by the per-cell flips — no falling / matrix scroll.
    const TERMS = ['ROI','ROAS','CTR','CPM','CPC','CPL','CPA','LTV','CAC','CR','KPI','SMM','DAU','MAU','GMV','ARPU','CPV','ER','КОНВЕРСИЯ','ОХВАТ','ЛИДЫ','ТРАФИК','ВЫРУЧКА','РОСТ','ЦЕЛЬ','КЛИКИ','ПОКАЗЫ','ВОРОНКА'];
    const randTerm = () => TERMS[(Math.random() * TERMS.length) | 0];

    targets.forEach((target) => {
      const isMark = target.classList.contains('hero-mark-wrap');

      let host, bgEl, overlay, intervalId;

      if (isMark) {
        // host = .hero-mark (so the overlay aligns with the actual mark, not the wider wrap)
        host = target.querySelector('.hero-mark') || target;
        bgEl = host.querySelector('.hero-mark-stage');
        host.classList.add('xray-host', 'xray-host-mark');

        overlay = document.createElement('div');
        overlay.className = 'xray-overlay xray-overlay-mark';
        overlay.setAttribute('aria-hidden', 'true');
        const inner = document.createElement('div');
        inner.className = 'xray-overlay-inner';
        // Dense field of digits, clipped to the eye silhouette via mask-image.
        const digits = new Array(900);
        for (let i = 0; i < digits.length; i++) digits[i] = `<span class="xd">${randDigit()}</span>`;
        inner.innerHTML = digits.join('');
        overlay.appendChild(inner);
        host.appendChild(overlay);
      } else {
        // Text host. Wrap existing content in .xray-bg (the visible white text),
        // then stack an identical copy whose glyphs are *filled* with the digit
        // pattern via background-clip:text. The lens mask reveals it only at the cursor.
        host = target;
        host.classList.add('xray-host', 'xray-host-text');

        bgEl = document.createElement('span');
        bgEl.className = 'xray-bg';
        while (host.firstChild) bgEl.appendChild(host.firstChild);
        host.appendChild(bgEl);

        // Exact clone — same letters, same positions — so the fill matches the glyphs.
        overlay = bgEl.cloneNode(true);
        overlay.className = 'xray-overlay xray-overlay-text';
        overlay.setAttribute('aria-hidden', 'true');
        host.appendChild(overlay);
      }

      // ── In-place digit field for the text variant ──────────────────────────
      // The glyph fill is a static grid of monospace chars sized to the text box.
      // Each tick only a handful of cells flip value (digits "tick" in place) and
      // a marketing term may surface — nothing scrolls, so it reads as numbers
      // living *inside* the letters rather than a matrix rain.
      const FCHAR = 6, FROW = 9, FSIZE = 9;
      let fW = 0, fH = 0, fCols = 0, fRows = 0, fGrid = [];
      const buildField = () => {
        const r = overlay.getBoundingClientRect();
        fW = Math.max(1, Math.ceil(r.width));
        fH = Math.max(1, Math.ceil(r.height));
        fCols = Math.max(1, Math.ceil(fW / FCHAR));
        fRows = Math.max(1, Math.ceil(fH / FROW));
        fGrid = [];
        for (let ri = 0; ri < fRows; ri++) {
          const row = new Array(fCols);
          for (let ci = 0; ci < fCols; ci++) row[ci] = randDigit();
          fGrid.push(row);
        }
      };
      const stampTerm = () => {
        const term = randTerm();
        if (fCols < term.length + 2) return;
        const ri = (Math.random() * fRows) | 0;
        const ci = (Math.random() * (fCols - term.length)) | 0;
        for (let k = 0; k < term.length; k++) fGrid[ri][ci + k] = term[k];
      };
      const renderField = () => {
        let texts = '';
        for (let ri = 0; ri < fRows; ri++) {
          const y = ri * FROW + (FROW - 2);
          const op = (0.62 + (ri % 3) * 0.13).toFixed(2);
          const str = fGrid[ri].join('');
          texts += `<text x='0' y='${y}' textLength='${fCols * FCHAR}' lengthAdjust='spacing' font-family='ui-monospace,monospace' font-size='${FSIZE}' fill='rgba(255,42,42,${op})' xml:space='preserve'>${str}</text>`;
        }
        const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='${fW}' height='${fH}'>${texts}</svg>`;
        overlay.style.backgroundImage = `url("data:image/svg+xml;utf8,${encodeURIComponent(svg)}"), linear-gradient(rgba(10,10,11,0.94),rgba(10,10,11,0.94))`;
        overlay.style.backgroundRepeat = 'no-repeat';
        overlay.style.backgroundPosition = '0 0';
        overlay.style.backgroundSize = `${fW}px ${fH}px, 100% 100%`;
      };
      if (!isMark) { buildField(); renderField(); }

      const setVars = (x, y) => {
        host.style.setProperty('--mx', x + 'px');
        host.style.setProperty('--my', y + 'px');
      };
      setVars(-2000, -2000);

      const onMove = (e) => {
        // Coords relative to the host (where the mask is applied).
        const r = host.getBoundingClientRect();
        setVars(e.clientX - r.left, e.clientY - r.top);
      };

      const scrambleTick = () => {
        if (isMark) {
          const spans = overlay.querySelectorAll('.xd');
          if (!spans.length) return;
          const n = Math.min(120, spans.length);
          for (let i = 0; i < n; i++) {
            const s = spans[(Math.random() * spans.length) | 0];
            s.textContent = randDigit();
          }
        } else {
          // Flip many cells in place each tick (fast "тык-тык"); surface a term or
          // two. Nothing scrolls — the glyphs stay packed, only the values churn.
          if (!fRows) buildField();
          const flips = Math.max(12, (fCols * fRows / 12) | 0);
          for (let k = 0; k < flips; k++) {
            const ri = (Math.random() * fRows) | 0;
            const ci = (Math.random() * fCols) | 0;
            fGrid[ri][ci] = randDigit();
          }
          stampTerm();
          if (Math.random() < 0.6) stampTerm();
          renderField();
        }
      };

      const onEnter = () => {
        host.classList.add('xray-active');
        if (!isMark) { buildField(); renderField(); }
        intervalId = setInterval(scrambleTick, isMark ? 80 : 55);
      };
      const onLeave = () => {
        host.classList.remove('xray-active');
        if (intervalId) { clearInterval(intervalId); intervalId = null; }
        setVars(-2000, -2000);
      };

      target.addEventListener('mousemove', onMove);
      target.addEventListener('mouseenter', onEnter);
      target.addEventListener('mouseleave', onLeave);

      cleanups.push(() => {
        target.removeEventListener('mousemove', onMove);
        target.removeEventListener('mouseenter', onEnter);
        target.removeEventListener('mouseleave', onLeave);
        if (intervalId) clearInterval(intervalId);
        overlay.remove();
        host.classList.remove('xray-host', 'xray-host-mark', 'xray-host-text', 'xray-active');
      });
    });

    return () => cleanups.forEach(fn => fn());
  }, []);
}

// nav scrolled state
function useNavScroll() {
  useEffect(() => {
    const nav = document.querySelector('.nav');
    const onScroll = () => {
      if (window.scrollY > 40) nav.classList.add('scrolled');
      else nav.classList.remove('scrolled');
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
}

Object.assign(window, { CustomCursor, useReveal, useMagnetic, useParallax, useFollowGlow, useNavScroll, useEyesFollowCursor, useXrayScanner });
