/* Shared animation primitives + icons for the Prisma landing page.
   Reproduces the framer-motion behaviors described in the spec using
   React state + IntersectionObserver + scroll math, exported to window. */
const { useState, useEffect, useRef } = React;

const EASE_EXPO = 'cubic-bezier(0.16, 1, 0.3, 1)';
const EASE_CARD = 'cubic-bezier(0.22, 1, 0.36, 1)';

/* "useInView once" — IntersectionObserver with robust fallbacks so revealed
   content can never stay permanently hidden if IO callbacks don't fire. */
function useInViewOnce(rootMargin = '0px') {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let done = false;
    const reveal = () => {
      if (done) return;
      done = true;
      setInView(true);
      cleanup();
    };
    // Parse a single px margin (e.g. "-100px") for the manual check.
    const m = parseFloat(rootMargin) || 0;
    const check = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || document.documentElement.clientHeight;
      if (r.top < vh - m && r.bottom > m) reveal();
    };
    const io = ('IntersectionObserver' in window)
      ? new IntersectionObserver(([entry]) => { if (entry.isIntersecting) reveal(); },
          { rootMargin, threshold: 0.01 })
      : null;
    if (io) io.observe(el);
    window.addEventListener('scroll', check, { passive: true });
    window.addEventListener('resize', check);
    // Immediate + deferred manual checks (covers IO not firing in some hosts).
    check();
    requestAnimationFrame(check);
    const t1 = setTimeout(check, 300);
    const t2 = setTimeout(reveal, 1200); // hard safety: never leave content hidden
    function cleanup() {
      if (io) io.disconnect();
      window.removeEventListener('scroll', check);
      window.removeEventListener('resize', check);
      clearTimeout(t1);
      clearTimeout(t2);
    }
    return cleanup;
  }, []);
  return [ref, inView];
}

/* After `active` turns true, wait `delay` ms then return true. Used to strip the
   CSS transition once the entrance animation should be finished — guarantees the
   element commits to its final (visible) state even in hosts where the CSS
   transition clock is frozen (JS timers still run). */
function useSettle(active, delay = 1600) {
  const [settled, setSettled] = useState(false);
  useEffect(() => {
    if (!active) return;
    const t = setTimeout(() => setSettled(true), delay);
    return () => clearTimeout(t);
  }, [active]);
  return settled;
}

/* ---------- Icons (lucide-style thin line) ---------- */
function ArrowRight({ className = '', style = {}, strokeWidth = 2 }) {
  return (
    <svg className={className} style={style} viewBox="0 0 24 24" fill="none"
      stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round"
      strokeLinejoin="round" width="1em" height="1em" aria-hidden="true">
      <line x1="5" y1="12" x2="19" y2="12" />
      <polyline points="12 5 19 12 12 19" />
    </svg>
  );
}
function Check({ className = '', style = {}, strokeWidth = 2.5 }) {
  return (
    <svg className={className} style={style} viewBox="0 0 24 24" fill="none"
      stroke="currentColor" strokeWidth={strokeWidth} strokeLinecap="round"
      strokeLinejoin="round" width="1em" height="1em" aria-hidden="true">
      <polyline points="20 6 9 17 4 12" />
    </svg>
  );
}

/* Render a word with a superscript asterisk after its final character. */
function renderWithAsterisk(word) {
  const head = word.slice(0, -1);
  const last = word.slice(-1);
  return (
    <React.Fragment>
      {head}
      <span style={{ position: 'relative' }}>
        {last}
        <span style={{
          position: 'absolute', top: '0.65em', right: '-0.3em',
          fontSize: '0.31em', lineHeight: 1
        }}>*</span>
      </span>
    </React.Fragment>
  );
}

/* ---------- WordsPullUp ---------- */
function WordsPullUp({ text, className = '', wordClassName = '', showAsterisk = false }) {
  const [ref, inView] = useInViewOnce('0px');
  const words = text.split(' ');
  const settled = useSettle(inView, 600 + words.length * 80 + 500);
  return (
    <span ref={ref} className={className} style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
      {words.map((word, wi) => {
        const isLast = wi === words.length - 1;
        return (
          <span key={wi} style={{ display: 'inline-flex', overflow: 'hidden', paddingBottom: '0.08em' }}>
            <span className={wordClassName} style={{
              display: 'inline-block',
              whiteSpace: 'pre',
              marginRight: isLast ? 0 : '0.22em',
              transform: inView ? 'translateY(0)' : 'translateY(20px)',
              opacity: inView ? 1 : 0,
              transition: settled ? 'none' : `transform 0.6s ${EASE_EXPO} ${wi * 0.08}s, opacity 0.6s ${EASE_EXPO} ${wi * 0.08}s`
            }}>
              {isLast && showAsterisk ? renderWithAsterisk(word) : word}
            </span>
          </span>
        );
      })}
    </span>
  );
}

/* ---------- WordsPullUpMultiStyle ---------- */
function WordsPullUpMultiStyle({ segments, className = '', align = 'center' }) {
  const [ref, inView] = useInViewOnce('0px');
  const words = [];
  segments.forEach((seg) => {
    seg.text.split(' ').forEach((w) => {
      if (w !== '') words.push({ w, cls: seg.className || '' });
    });
  });
  const settled = useSettle(inView, 600 + words.length * 80 + 500);
  return (
    <span ref={ref} className={className}
      style={{ display: 'inline-flex', flexWrap: 'wrap', justifyContent: align }}>
      {words.map((it, i) => (
        <span key={i} style={{ display: 'inline-flex', overflow: 'hidden', paddingBottom: '0.12em' }}>
          <span className={it.cls} style={{
            display: 'inline-block',
            whiteSpace: 'pre',
            marginRight: '0.26em',
            transform: inView ? 'translateY(0)' : 'translateY(20px)',
            opacity: inView ? 1 : 0,
            transition: settled ? 'none' : `transform 0.6s ${EASE_EXPO} ${i * 0.08}s, opacity 0.6s ${EASE_EXPO} ${i * 0.08}s`
          }}>{it.w}</span>
        </span>
      ))}
    </span>
  );
}

/* ---------- ScrollRevealText (per-character scroll-linked opacity) ----------
   Accepts either `text` (plain string) or `segments` ([{ text, className }]) so
   styled runs (e.g. an italic accent) keep the per-character parallax reveal. */
function ScrollRevealText({ text, segments, className = '', style = {} }) {
  const ref = useRef(null);
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const el = ref.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const startTop = 0.8 * vh;              // progress 0 when top hits 80% down
      const endTop = 0.2 * vh - rect.height;  // progress 1 when bottom hits 20% down
      let p = (startTop - rect.top) / (startTop - endTop);
      setProgress(Math.max(0, Math.min(1, p)));
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
    };
  }, []);
  const parts = segments && segments.length ? segments : [{ text: text || '', className: '' }];
  const chars = [];
  parts.forEach((seg) => {
    const lines = (seg.text || '').split('\n');
    lines.forEach((line, li) => {
      if (li > 0) chars.push({ br: true });
      [...line].forEach((ch) => chars.push({ ch, cls: seg.className || '' }));
    });
  });
  const total = chars.length;
  return (
    <p ref={ref} className={className} style={style}>
      {chars.map((item, i) => {
        if (item.br) return <br key={i} />;
        const cp = i / total;
        const local = (progress - (cp - 0.1)) / 0.15; // range [cp-0.1, cp+0.05]
        const o = 0.2 + 0.8 * Math.max(0, Math.min(1, local));
        return <span key={i} className={item.cls} style={{ opacity: o }}>{item.ch}</span>;
      })}
    </p>
  );
}

/* ---------- CardReveal (scale + fade entrance, staggered) ---------- */
function CardReveal({ index = 0, children, className = '', style = {} }) {
  const [ref, inView] = useInViewOnce('-100px');
  const settled = useSettle(inView, 700 + index * 150 + 400);
  return (
    <div ref={ref} className={className} style={{
      transform: inView ? 'scale(1)' : 'scale(0.95)',
      opacity: inView ? 1 : 0,
      transition: settled ? 'none' : `transform 0.7s ${EASE_CARD} ${index * 0.15}s, opacity 0.7s ${EASE_CARD} ${index * 0.15}s`,
      ...style
    }}>{children}</div>
  );
}

/* ---------- FadeUp (timed mount fade for hero content) ---------- */
function FadeUp({ delay = 0, as = 'div', children, className = '', style = {} }) {
  const [shown, setShown] = useState(false);
  useEffect(() => {
    const t = setTimeout(() => setShown(true), 60);
    return () => clearTimeout(t);
  }, []);
  const settled = useSettle(shown, delay * 1000 + 800 + 400);
  const El = as;
  return (
    <El className={className} style={{
      transform: shown ? 'translateY(0)' : 'translateY(20px)',
      opacity: shown ? 1 : 0,
      transition: settled ? 'none' : `transform 0.8s ${EASE_EXPO} ${delay}s, opacity 0.8s ${EASE_EXPO} ${delay}s`,
      ...style
    }}>{children}</El>
  );
}

Object.assign(window, {
  useInViewOnce, ArrowRight, Check,
  WordsPullUp, WordsPullUpMultiStyle, ScrollRevealText, CardReveal, FadeUp
});
