const { useState: useCS, useEffect: useCE, useRef: useCR } = React;

/* ---------- Reveal: intersection-based fade/slide (light page) ---------- */
function Reveal({ children, delay = 0, as: As = 'div', className = '', ...rest }) {
  const ref = useCR(null);
  useCE(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) {
          e.target.classList.add('in');
          io.unobserve(e.target);
        }
      });
    }, { threshold: 0.12 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <As ref={ref} className={`reveal ${className}`} style={{transitionDelay: `${delay}ms`}} {...rest}>
      {children}
    </As>
  );
}

/* ---------- Word-by-word title reveal ---------- */
function WordReveal({ children, className = '', as: As = 'h2', delay = 0 }) {
  const ref = useCR(null);
  const [shown, setShown] = useCS(false);
  useCE(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting) { setShown(true); io.unobserve(e.target); }
      });
    }, { threshold: 0.18 });
    io.observe(el);
    return () => io.disconnect();
  }, []);

  let wordIdx = 0;
  const renderNode = (node) => {
    if (typeof node === 'string') {
      return node.split(/(\s+)/).map((part) => {
        if (/^\s+$/.test(part)) return part;
        if (part === '') return null;
        const idx = wordIdx++;
        return (
          <span key={`w-${idx}`} className="wr-word"
                style={{ transitionDelay: `${delay + idx * 55}ms`, animationDelay: `${delay + idx * 55}ms` }}>
            <span className="wr-inner">{part}</span>
          </span>
        );
      });
    }
    if (React.isValidElement(node)) {
      return React.cloneElement(node, { key: `el-${wordIdx}` }, React.Children.map(node.props.children, renderNode));
    }
    if (Array.isArray(node)) return node.map(renderNode);
    return node;
  };

  return (
    <As ref={ref} className={`wr ${shown ? 'in' : ''} ${className}`}>
      {React.Children.map(children, renderNode)}
    </As>
  );
}

/* ---------- ChapterHead: mono index + animated title + lede ---------- */
function ChapterHead({ chapter, label, title, lede, ledeRight = true }) {
  return (
    <div className="ch-head">
      <div className="ch-mark">
        <span className="ch-num mono">{chapter}</span>
        <span className="ch-sep mono">—</span>
        <span className="ch-label mono">{label}</span>
      </div>

      <div className={`ch-body ${lede && ledeRight ? 'split' : ''}`}>
        <WordReveal as="h2" className="ch-title">{title}</WordReveal>
        {lede && (
          <Reveal delay={200}>
            <p className="ch-lede">{lede}</p>
          </Reveal>
        )}
      </div>

      <style>{`
        .ch-head { position: relative; margin-bottom: 56px; }
        .ch-mark {
          margin-bottom: 24px;
          display: inline-flex;
          align-items: baseline;
          gap: 12px;
          padding-bottom: 14px;
          border-bottom: 1px solid var(--line);
        }
        .ch-num { font-size: 11px; color: var(--ink-3); letter-spacing: 0.14em; font-weight: 500; }
        .ch-sep { font-size: 11px; color: var(--line-2); }
        .ch-label { font-size: 11px; color: var(--ink-2); letter-spacing: 0.18em; text-transform: uppercase; font-weight: 500; }

        .ch-body { display: grid; grid-template-columns: 1.5fr 1fr; gap: 56px; align-items: end; }
        .ch-body:not(.split) { grid-template-columns: 1fr; }
        @media (max-width: 900px) { .ch-body { grid-template-columns: 1fr; gap: 20px; } }

        .ch-title {
          font-family: var(--serif);
          font-size: clamp(40px, 5.2vw, 76px);
          line-height: 1.04;
          letter-spacing: -0.025em;
          color: var(--ink);
          margin: 0;
          text-wrap: balance;
          font-weight: 400;
          max-width: 18ch;
        }
        .ch-title em { font-style: italic; color: var(--ink-2); }
        .ch-lede {
          color: var(--ink-2);
          font-size: 16px;
          line-height: 1.6;
          max-width: 44ch;
          padding-bottom: 10px;
          margin: 0;
          text-wrap: pretty;
        }

        /* word-by-word reveal */
        .wr-word { display: inline-block; overflow: hidden; vertical-align: top; }
        .wr-word .wr-inner {
          display: inline-block;
          transform: translateY(108%);
          opacity: 0;
          transition: transform 0.8s cubic-bezier(0.2,0.7,0.2,1), opacity 0.5s ease;
        }
        .wr.in .wr-word .wr-inner { transform: none; opacity: 1; }
        body.no-motion .wr-word .wr-inner { transform: none; opacity: 1; transition: none; }
      `}</style>
    </div>
  );
}

/* ---------- Counter: count-up on enter (time-based, never counts down) ---------- */
function Counter({ to, suffix = '', decimals = 0, duration = 1400, sep = '' }) {
  const [val, setVal] = useCS(0);
  const ref = useCR(null);
  const started = useCR(false);
  useCE(() => {
    if (!ref.current) return;
    if (document.body.classList.contains('no-motion')) { setVal(to); started.current = true; return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => {
        if (e.isIntersecting && !started.current) {
          started.current = true;
          const start = performance.now();
          const tick = (now) => {
            const t = Math.min((now - start) / duration, 1);
            const eased = 1 - Math.pow(1 - t, 3);
            setVal(to * eased);
            if (t < 1) requestAnimationFrame(tick);
          };
          requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.4 });
    io.observe(ref.current);
    return () => io.disconnect();
  }, [to, duration]);
  const display = decimals
    ? val.toFixed(decimals)
    : (sep ? String(Math.round(val)).replace(/\B(?=(\d{3})+(?!\d))/g, sep) : Math.round(val));
  return <span ref={ref}>{display}{suffix}</span>;
}

/* ---------- Scroll progress bar (top) — thin, navy, no glow ---------- */
function ScrollProgress() {
  const [p, setP] = useCS(0);
  useCE(() => {
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => {
        raf = 0;
        const doc = document.documentElement;
        const max = doc.scrollHeight - doc.clientHeight;
        setP(max > 0 ? window.scrollY / max : 0);
      });
    };
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => { window.removeEventListener('scroll', onScroll); if (raf) cancelAnimationFrame(raf); };
  }, []);
  return (
    <div className="scroll-prog" style={{transform: `scaleX(${p})`}}>
      <style>{`
        .scroll-prog {
          position: fixed; top: 0; left: 0; right: 0;
          height: 2px;
          background: var(--accent);
          opacity: 0.7;
          transform-origin: 0 50%;
          z-index: 60;
          pointer-events: none;
          transition: transform 0.1s linear;
        }
      `}</style>
    </div>
  );
}

/* no-op kept for back-compat */
function SectionWorld() { return null; }

Object.assign(window, { Reveal, WordReveal, ChapterHead, Counter, ScrollProgress, SectionWorld });
