/* ============================================================
   Calypsos Prototype — shared primitives
   Reveal-on-scroll hook, sticky-progress hook, tiny utilities.
   ============================================================ */

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

/* useReveal — scroll-linked reveal. Drives opacity + translateY via inline
   styles directly from scroll position (no CSS transition, no document
   timeline — works even when the preview iframe is paused). */
function useReveal(ref, opts = {}) {
  const { distance = 28, range = 220, startOffset = 60 } = opts;
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    // Find scroll container
    let container = null;
    let p = el.parentElement;
    while (p) {
      const cs = getComputedStyle(p);
      if (/(auto|scroll)/.test(cs.overflowY)) { container = p; break; }
      p = p.parentElement;
    }
    const useViewport = !container;
    if (useViewport) container = window;

    // Detect variant from class for transform style
    const cls = el.className || '';
    const variant =
      cls.includes('r-scale') ? 'scale' :
      cls.includes('r-right') ? 'right' :
      cls.includes('r-fade')  ? 'fade'  :
      cls.includes('r-up')    ? 'up'    : 'default';

    const getBounds = () => {
      if (useViewport) return { top: 0, bottom: window.innerHeight };
      const r = container.getBoundingClientRect();
      return { top: r.top, bottom: r.bottom };
    };

    const update = () => {
      const rect = el.getBoundingClientRect();
      const b = getBounds();
      // Progress: 0 when element top is at (bottom - startOffset), 1 when at (bottom - range).
      const startY = b.bottom - startOffset;
      const endY   = b.bottom - range;
      let pct = (startY - rect.top) / Math.max(1, startY - endY);
      pct = Math.max(0, Math.min(1, pct));
      // Apply inline
      el.style.opacity = String(pct);
      const yDist = variant === 'up' ? 48 : distance;
      switch (variant) {
        case 'fade':
          el.style.transform = 'none';
          break;
        case 'scale':
          el.style.transform = `scale(${0.96 + 0.04 * pct})`;
          el.style.transformOrigin = 'center bottom';
          break;
        case 'right':
          el.style.transform = `translateX(${(1 - pct) * -32}px)`;
          break;
        default:
          el.style.transform = `translateY(${(1 - pct) * yDist}px)`;
      }
    };

    const target = useViewport ? window : container;
    target.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update, { passive: true });
    // initial state
    update();
    return () => {
      target.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, []);
}

/* <Reveal> — wraps children in a reveal-on-scroll div */
function Reveal({ children, delay = 0, variant = '', as = 'div', style, className = '', ...rest }) {
  const ref = useRef(null);
  useReveal(ref);
  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={`r ${variant} ${className}`}
      style={{ ...style, '--reveal-delay': `${delay}ms` }}
      {...rest}
    >
      {children}
    </Tag>
  );
}

/* useStickyProgress — for a scroll-pinned section, returns 0..1 progress
   based on how much the column has scrolled past the section's top.
   Scroll-linked, no rAF/timeline reliance. */
function useStickyProgress(ref) {
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let container = null;
    let p = el.parentElement;
    while (p) {
      const cs = getComputedStyle(p);
      if (/(auto|scroll)/.test(cs.overflowY)) { container = p; break; }
      p = p.parentElement;
    }
    if (!container) return;
    const update = () => {
      const rect = el.getBoundingClientRect();
      const cRect = container.getBoundingClientRect();
      const topRel = rect.top - cRect.top;
      const total = rect.height - container.clientHeight;
      if (total <= 0) { setProgress(0); return; }
      const raw = -topRel / total;
      setProgress(Math.max(0, Math.min(1, raw)));
    };
    container.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update, { passive: true });
    update();
    return () => {
      container.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, []);
  return progress;
}

/* Tiny shorthand for an inline hairline rule */
function Hair({ color = 'currentColor', opacity = 0.14, style = {}, ...rest }) {
  return <div style={{ height: 1, background: color, opacity, ...style }} {...rest} />;
}

/* Count-up number on reveal. Scroll-linked: progresses from 0 → to as the
   element scrolls into the container's viewport. No document-timeline reliance. */
function CountUp({ to, suffix = '', prefix = '', decimals = 0, format, range = 280 }) {
  const ref = useRef(null);
  const [val, setVal] = useState(to);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let container = null;
    let p = el.parentElement;
    while (p) {
      const cs = getComputedStyle(p);
      if (/(auto|scroll)/.test(cs.overflowY)) { container = p; break; }
      p = p.parentElement;
    }
    const useViewport = !container;
    if (useViewport) container = window;
    const getB = () => useViewport
      ? { bottom: window.innerHeight }
      : { bottom: container.getBoundingClientRect().bottom };
    const update = () => {
      const rect = el.getBoundingClientRect();
      const b = getB();
      const startY = b.bottom - 40;
      const endY   = b.bottom - range;
      let pct = (startY - rect.top) / Math.max(1, startY - endY);
      pct = Math.max(0, Math.min(1, pct));
      const eased = 1 - Math.pow(1 - pct, 3);
      setVal(to * eased);
    };
    const target = useViewport ? window : container;
    target.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update, { passive: true });
    update();
    return () => {
      target.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, [to, range]);
  const txt = format ? format(val) : val.toFixed(decimals);
  return <span ref={ref}>{prefix}{txt}{suffix}</span>;
}

/* Pinned section helper: a tall outer wrapper that contains a sticky inner
   stage. Children render inside the sticky stage; given the column-scroll
   progress as `t` (0..1). */
function Pinned({ height = 3, children, style }) {
  // height in viewport units (e.g. 3 = 300vh of outer scroll, gives 200vh of pin)
  const outerRef = useRef(null);
  const t = useStickyProgress(outerRef);
  return (
    <div ref={outerRef} style={{ position: 'relative', height: `${height * 100}vh`, ...style }}>
      <div style={{ position: 'sticky', top: 0, height: '100vh', overflow: 'hidden' }}>
        {children(t)}
      </div>
    </div>
  );
}

/* Mini SVG: the Vessel "V" mark, used by both prototypes */
function VMark({ color = 'currentColor', size = 28 }) {
  return (
    <svg width={size * 1.05} height={size} viewBox="0 0 21 20" fill="none" style={{ display: 'block' }}>
      <path d="M0.6 0.6 H4.9 L10.5 14.5 L16.1 0.6 H20.4 L11.9 19.4 H9.1 Z" fill={color} />
    </svg>
  );
}

Object.assign(window, { Reveal, useReveal, useStickyProgress, Pinned, Hair, CountUp, VMark });
