// Shared primitives and hooks

const { useEffect, useRef, useState, useMemo, useCallback } = React;

function useScrollY() {
  const [y, setY] = useState(0);
  useEffect(() => {
    const onScroll = () => setY(window.scrollY);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return y;
}

// Progress of an element through the viewport: 0 when entering bottom, 1 when leaving top
function useElementProgress(ref, opts = {}) {
  const [p, setP] = useState(0);
  useEffect(() => {
    const compute = () => {
      const el = ref.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      // 0 when top reaches bottom of viewport; 1 when bottom reaches top
      const total = rect.height + vh;
      const scrolled = vh - rect.top;
      const prog = Math.max(0, Math.min(1, scrolled / total));
      setP(prog);
    };
    compute();
    window.addEventListener('scroll', compute, { passive: true });
    window.addEventListener('resize', compute);
    return () => {
      window.removeEventListener('scroll', compute);
      window.removeEventListener('resize', compute);
    };
  }, [ref]);
  return p;
}

// Progress during the sticky lifetime of a tall section.
// For a section with height=H and viewport vh, sticky child is pinned for (H - vh).
// Returns 0 before sticky, 0..1 during, 1 after.
function useStickyProgress(sectionRef) {
  const [p, setP] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const el = sectionRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const total = rect.height - vh;
      if (total <= 0) { setP(0); return; }
      const scrolled = -rect.top;
      const prog = Math.max(0, Math.min(1, scrolled / total));
      setP(prog);
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
    };
  }, [sectionRef]);
  return p;
}

function useInView(ref, threshold = 0.3) {
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const obs = new IntersectionObserver(
      ([e]) => setInView(e.isIntersecting),
      { threshold }
    );
    obs.observe(ref.current);
    return () => obs.disconnect();
  }, [ref, threshold]);
  return inView;
}

function useResponsive() {
  const getViewport = () => {
    const width = window.innerWidth || document.documentElement.clientWidth || 0;
    const height = window.innerHeight || document.documentElement.clientHeight || 0;
    const coarse = window.matchMedia ? window.matchMedia('(pointer: coarse)').matches : false;
    return {
      width,
      height,
      isPhone: width <= 760,
      isTablet: width <= 1100,
      isTouch: coarse
    };
  };

  const [viewport, setViewport] = useState(getViewport);

  useEffect(() => {
    let raf;
    const update = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => setViewport(getViewport()));
    };

    update();
    window.addEventListener('resize', update);
    window.addEventListener('orientationchange', update);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', update);
      window.removeEventListener('orientationchange', update);
    };
  }, []);

  return viewport;
}

function clamp(v, a, b) { return Math.max(a, Math.min(b, v)); }
function lerp(a, b, t) { return a + (b - a) * t; }
function easeOut(t) { return 1 - Math.pow(1 - t, 3); }
function easeInOut(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }

// SplitText-style: reveal by word/char on entering viewport
function Reveal({ children, as = 'div', delay = 0, className = '', y = 40 }) {
  const ref = useRef(null);
  const inView = useInView(ref, 0.2);
  const Tag = as;
  return (
    <Tag ref={ref} className={className} style={{
      display: 'inline-block',
      transform: inView ? 'translateY(0)' : `translateY(${y}px)`,
      opacity: inView ? 1 : 0,
      transition: `transform 1.2s cubic-bezier(0.25,0.46,0.45,0.94) ${delay}s, opacity 1.2s ease ${delay}s`,
      willChange: 'transform, opacity'
    }}>
      {children}
    </Tag>
  );
}

// Word-by-word reveal
function RevealWords({ text, className = '', wordClass = '', baseDelay = 0, step = 0.08 }) {
  const ref = useRef(null);
  const inView = useInView(ref, 0.2);
  const words = text.split(' ');
  return (
    <span ref={ref} className={className}>
      {words.map((w, i) => (
        <span key={i} style={{ display: 'inline-block', overflow: 'hidden', verticalAlign: 'bottom' }}>
          <span style={{
            display: 'inline-block',
            transform: inView ? 'translateY(0)' : 'translateY(110%)',
            transition: `transform 0.9s cubic-bezier(0.25,0.46,0.45,0.94) ${baseDelay + i * step}s`,
            willChange: 'transform'
          }} className={wordClass}>
            {w}{i < words.length - 1 ? '\u00A0' : ''}
          </span>
        </span>
      ))}
    </span>
  );
}

Object.assign(window, {
  useScrollY, useElementProgress, useStickyProgress, useInView,
  useResponsive,
  clamp, lerp, easeOut, easeInOut,
  Reveal, RevealWords
});
