// Press-and-hold hook + interactive Hero wrapper.
// The "press" is satisfying: smooth ease-in to "live", quick decay on release.

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

function usePressHold({ rampUp = 1.4, rampDown = 0.6 } = {}) {
  const [pressed, setPressed] = useState(false);
  const [t, setT] = useState(0);
  const tRef = useRef(0);
  const rafRef = useRef(0);
  const lastRef = useRef(0);
  const pressedRef = useRef(false);

  useEffect(() => {
    const tick = (ts) => {
      const last = lastRef.current || ts;
      const dt = (ts - last) / 1000;
      lastRef.current = ts;
      const target = pressedRef.current ? 1 : 0;
      const rate = pressedRef.current ? 1 / rampUp : 1 / rampDown;
      let next = tRef.current + (target - tRef.current) * Math.min(1, dt * rate * 4);
      // Smoother: exponential approach
      next = tRef.current + (target - tRef.current) * (1 - Math.exp(-dt * rate * 5));
      tRef.current = Math.max(0, Math.min(1, next));
      setT(tRef.current);
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [rampUp, rampDown]);

  const press = useCallback(() => { pressedRef.current = true; setPressed(true); }, []);
  const release = useCallback(() => { pressedRef.current = false; setPressed(false); }, []);

  const handlers = {
    onMouseDown: press,
    onMouseUp: release,
    onMouseLeave: release,
    onTouchStart: (e) => { e.preventDefault(); press(); },
    onTouchEnd: release,
    onKeyDown: (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); press(); } },
    onKeyUp: (e) => { if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); release(); } },
  };

  return { pressed, t, handlers };
}

window.usePressHold = usePressHold;

// useScrollProgress(ref) — returns 0..1 based on the section's scroll-through
// position. 0 when the section's top is at or below the bottom of the viewport
// (not yet scrolled into view). 1 when the section's bottom has scrolled past
// the top of the viewport. Until first scroll, sits at 0.
//
// useGlobalScrollProgress() — returns 0..1 based on overall page scroll. 0 at
// page top, 1 when scrolled to bottom.
function useScrollProgress(ref, opts = {}) {
  const { startOffset = 0.85, endOffset = 0.15, anchor = 'viewport' } = opts;
  const [p, setP] = useState(0);
  useEffect(() => {
    const update = () => {
      const el = ref.current;
      if (!el) return;
      // 'top' anchor: progress is purely a function of how far the user has
      // scrolled past the element's top relative to its height. At scrollY=0
      // when the element is at the top of the page, progress = 0.
      if (anchor === 'top') {
        const r = el.getBoundingClientRect();
        const elTop = r.top + window.scrollY;
        const elHeight = r.height;
        const span = elHeight - window.innerHeight * endOffset;
        if (span <= 0) { setP(0); return; }
        const v = (window.scrollY - elTop) / span;
        setP(Math.max(0, Math.min(1, v)));
        return;
      }
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const start = vh * startOffset;
      const end = vh * endOffset - r.height;
      const denom = start - end;
      if (denom <= 0) { setP(r.top < start ? 1 : 0); return; }
      const v = (start - r.top) / denom;
      setP(Math.max(0, Math.min(1, v)));
    };
    update();
    window.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    return () => {
      window.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, [startOffset, endOffset, anchor]);
  return p;
}

function useGlobalScrollProgress() {
  const [p, setP] = useState(0);
  useEffect(() => {
    const update = () => {
      const max = document.documentElement.scrollHeight - window.innerHeight;
      if (max <= 0) { setP(0); return; }
      setP(Math.max(0, Math.min(1, window.scrollY / max)));
    };
    update();
    window.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    return () => {
      window.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, []);
  return p;
}

window.useScrollProgress = useScrollProgress;
window.useGlobalScrollProgress = useGlobalScrollProgress;

// ScrubPill — a small, persistent indicator showing scroll-driven progress.
// Visual: pill with a thin progress fill + state label.
const ScrubPill = ({ t, dark = false, label }) => {
  const state = t < 0.02 ? 'idle' : t < 0.95 ? 'live' : 'done';
  const text = label || (state === 'idle' ? 'IDLE' : state === 'live' ? 'LIVE' : 'COMPLETE');
  const bg = dark ? 'var(--ex-cream)' : 'var(--ex-ink)';
  const fg = dark ? 'var(--ex-ink)' : 'var(--ex-cream)';
  return (
    <div style={{
      position: 'relative',
      display: 'inline-flex', alignItems: 'center', gap: 10,
      padding: '10px 18px 10px 14px',
      background: bg, color: fg, borderRadius: 999,
      fontFamily: 'var(--ex-mono)', fontSize: 11,
      letterSpacing: '0.08em', textTransform: 'uppercase',
      overflow: 'hidden', userSelect: 'none',
    }}>
      {/* fill bar */}
      <div style={{
        position: 'absolute', left: 0, top: 0, bottom: 0,
        width: `${t * 100}%`,
        background: 'oklch(0.68 0.22 30 / 0.25)',
        transition: 'width 0.05s linear',
        pointerEvents: 'none',
      }}/>
      <span style={{
        position: 'relative',
        display: 'inline-block', width: 7, height: 7, borderRadius: '50%',
        background: state === 'idle' ? (dark ? 'rgba(0,0,0,0.25)' : 'rgba(255,255,255,0.25)') : 'var(--ex-signal)',
        boxShadow: state !== 'idle' ? '0 0 8px var(--ex-signal-glow)' : 'none',
        transition: 'background 0.2s, box-shadow 0.2s',
      }}/>
      <span style={{ position: 'relative' }}>{text}</span>
      <span style={{
        position: 'relative',
        fontVariantNumeric: 'tabular-nums', opacity: 0.55,
        marginLeft: 4, fontSize: 10,
      }}>{Math.round(t * 100).toString().padStart(2, '0')}%</span>
    </div>
  );
};

window.ScrubPill = ScrubPill;

// PinnedScrub — wrap an animated section so it pins to the viewport while
// scroll drives its animation 0→1. Once complete, scroll resumes normally.
//
//   <PinnedScrub scrubLength={1.5}>
//     {(t) => <YourAnimatedSection t={t}/>}
//   </PinnedScrub>
//
// scrubLength is multiples of one viewport. 1 = scroll one screen-height to
// complete the animation. The outer container is (scrubLength + 1) * 100vh
// tall: the +1 is the visible "pinned" window itself.
// dwellEnd: fraction of the scroll budget at the end where the animation
// stays at t=1 before the pin releases. e.g. dwellEnd=0.2 means the last
// 20% of scrolling sits at the fully-revealed state so people can take it in.
const PinnedScrub = ({ scrubLength = 1.2, dwellEnd = 0.2, children, background, sectionId }) => {
  const outerRef = useRef(null);
  const [isMobile, setIsMobile] = useState(() =>
    typeof window !== 'undefined' && window.matchMedia('(max-width: 760px)').matches
  );
  const [t, setT] = useState(0);
  useEffect(() => {
    const mql = window.matchMedia('(max-width: 760px)');
    const onChange = (e) => setIsMobile(e.matches);
    mql.addEventListener('change', onChange);
    return () => mql.removeEventListener('change', onChange);
  }, []);
  useEffect(() => {
    if (isMobile) { setT(1); return; }
    let raf = 0;
    let target = 0;
    const compute = () => {
      const el = outerRef.current;
      if (!el) return 0;
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const scrubPx = scrubLength * vh;
      if (scrubPx <= 0) return 0;
      const raw = Math.max(0, Math.min(1, -r.top / scrubPx));
      const active = Math.max(0.0001, 1 - dwellEnd);
      return Math.max(0, Math.min(1, raw / active));
    };
    const update = () => {
      target = compute();
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        setT((prev) => (Math.abs(prev - target) < 0.003 ? prev : target));
      });
    };
    update();
    window.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, [scrubLength, dwellEnd, isMobile]);

  return (
    <section
      id={sectionId}
      ref={outerRef}
      data-ex-pin
      style={{
        position: 'relative',
        height: `calc(${(scrubLength + 1) * 100}vh - var(--ex-nav-h))`,
        background: background || 'transparent',
      }}>
      <div data-ex-pin-inner style={{
        position: 'sticky', top: 'var(--ex-nav-h)',
        height: 'calc(100vh - var(--ex-nav-h))',
        overflow: 'hidden',
        display: 'flex', flexDirection: 'column',
      }}>
        {typeof children === 'function' ? children(t) : children}
      </div>
    </section>
  );
};

window.PinnedScrub = PinnedScrub;

// HeroFrame — full-bleed renderer for one hero variant, with the press pill
// and a small caption explaining the concept.
const HeroFrame = ({ Component, title, caption, pillLabel = 'PRESS & HOLD' }) => {
  const { pressed, t, handlers } = usePressHold();

  return (
    <div style={{
      position: 'relative',
      width: '100%', height: '100%',
      background: 'var(--ex-cream)',
      overflow: 'hidden',
    }}>
      <Component pressed={pressed} t={t} />

      {/* The pill - center bottom */}
      <div style={{
        position: 'absolute',
        left: '50%', bottom: 64,
        transform: 'translateX(-50%)',
        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
        zIndex: 5,
      }}>
        <button
          className="press-pill"
          data-pressed={pressed ? 'true' : 'false'}
          {...handlers}
        >
          <span className="dot"/>
          <span>{pressed ? 'HOLDING' : pillLabel}</span>
        </button>
        <div style={{
          fontFamily: 'var(--ex-mono)',
          fontSize: 9,
          color: 'var(--ex-mute)',
          letterSpacing: '0.08em',
          textTransform: 'uppercase',
        }}>
          {title}
        </div>
      </div>
    </div>
  );
};

window.HeroFrame = HeroFrame;
