/* global React, INK, CREAM, PULSE, Sparkle */
// Site shared bits: scroll reveals, counters, typing, section heads, product chrome (from Figma values).

const { useState: sbS, useEffect: sbE, useRef: sbR } = React;
const SB_EASE = 'cubic-bezier(0.2, 0.8, 0.2, 1)';

function useReveal(threshold = 0.22) {
  const ref = sbR(null);
  const [on, setOn] = sbS(false);
  sbE(() => {
    const el = ref.current;
    if (!el) return;
    let done = false;
    const fire = () => {if (!done) {done = true;setOn(true);cleanup();}};
    // primary: IntersectionObserver
    let io = null;
    try {
      io = new IntersectionObserver(([e]) => {if (e.isIntersecting) fire();}, { threshold });
      io.observe(el);
    } catch (e) {}
    // fallback: rect check on scroll/resize + rAF poll (covers webviews where IO never fires)
    const check = () => {
      const r = el.getBoundingClientRect();
      if (r.top < window.innerHeight * 0.92 && r.bottom > 0) fire();
    };
    const onScroll = () => check();
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll, { passive: true });
    const poll = setInterval(check, 600);
    check();
    function cleanup() {
      if (io) io.disconnect();
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      clearInterval(poll);
    }
    return cleanup;
  }, []);
  return [ref, on];
}

const Reveal = ({ children, delay = 0, y = 22, style, as = 'div' }) => {
  const [ref, on] = useReveal();
  return React.createElement(as, {
    ref,
    style: {
      opacity: on ? 1 : 0, transform: on ? 'none' : `translateY(${y}px)`,
      transition: `opacity 0.7s ${SB_EASE} ${delay}ms, transform 0.7s ${SB_EASE} ${delay}ms`, ...style
    }
  }, children);
};

function useCountUp(target, on, dur = 1500) {
  const [v, setV] = sbS(0);
  sbE(() => {
    if (!on) return;
    let raf;const t0 = performance.now();
    const step = (t) => {
      const p = Math.min(1, (t - t0) / dur);
      setV(Math.round(target * (1 - Math.pow(1 - p, 3))));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [on, target]);
  return v;
}

function useTyping(text, on, speed = 34, startDelay = 400) {
  const [i, setI] = sbS(0);
  sbE(() => {
    if (!on) {setI(0);return;}
    let t;let idx = 0;
    const go = () => {idx++;setI(idx);if (idx < text.length) t = setTimeout(go, speed + Math.random() * 30);};
    t = setTimeout(go, startDelay);
    return () => clearTimeout(t);
  }, [on, text]);
  return [text.slice(0, i), i >= text.length];
}

// Inertia-smoothed scroll progress: the value eases toward the real scroll position every frame
// (critically-damped lerp), so scroll-linked animations glide instead of snapping with the wheel.
function useSmoothScroll(damping = 0.19) {
  const ref = sbR(null);
  const [p, setP] = sbS(0);
  sbE(() => {
    let raf = 0,cur = 0,target = 0,running = false;
    const tick = () => {
      cur += (target - cur) * damping;
      if (Math.abs(target - cur) < 0.0004) {cur = target;running = false;} else {raf = requestAnimationFrame(tick);}
      const v = cur;
      setP((prev) => Math.abs(prev - v) < 0.0004 ? prev : v);
    };
    const measure = () => {
      const el = ref.current;
      if (!el) return;
      const total = el.offsetHeight - window.innerHeight;
      if (total <= 0) return;
      target = Math.min(1, Math.max(0, -el.getBoundingClientRect().top / total));
      if (!running) {running = true;cancelAnimationFrame(raf);raf = requestAnimationFrame(tick);}
    };
    measure();cur = target;setP(target);
    window.addEventListener('scroll', measure, { passive: true });
    window.addEventListener('resize', measure);
    return () => {window.removeEventListener('scroll', measure);window.removeEventListener('resize', measure);cancelAnimationFrame(raf);};
  }, []);
  return [ref, p];
}

// rAF-throttled scroll progress for pinned sections — one measurement per frame, no redundant renders.
// Pass a map fn (e.g. p => Math.floor(p * 6)) to re-render ONLY when the mapped value changes.
function useScrollProgress(map) {
  const ref = sbR(null);
  const [val, setVal] = sbS(map ? map(0) : 0);
  sbE(() => {
    let raf = 0;
    const measure = () => {
      raf = 0;
      const el = ref.current;
      if (!el) return;
      const total = el.offsetHeight - window.innerHeight;
      if (total <= 0) return;
      const p = Math.min(1, Math.max(0, -el.getBoundingClientRect().top / total));
      if (map) {
        const m = map(p);
        setVal((prev) => Object.is(prev, m) ? prev : m);
      } else {
        setVal((prev) => Math.abs(prev - p) < 0.0015 && p > 0 && p < 1 ? prev : p);
      }
    };
    const onScroll = () => {if (!raf) raf = requestAnimationFrame(measure);};
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll, { passive: true });
    measure();
    return () => {window.removeEventListener('scroll', onScroll);window.removeEventListener('resize', onScroll);if (raf) cancelAnimationFrame(raf);};
  }, []);
  return [ref, val];
}

const SectionHead = ({ num, title, sub, dark = false, center = false, wide = false }) =>
<div style={{ maxWidth: wide ? 900 : 640, margin: center ? '0 auto' : 0, textAlign: center ? 'center' : 'left' }}>
    <Reveal delay={80}>
      <h2 style={{ font: '800 52px/1 var(--font-sans)', letterSpacing: '-0.03em',
      color: dark ? '#fff' : INK, margin: '0 0 18px', textWrap: 'balance' }}>{title}</h2>
    </Reveal>
    <Reveal delay={160}>
      <p style={{ fontSize: 17, lineHeight: 1.55, color: dark ? 'rgba(255,255,255,0.78)' : 'var(--text-secondary)', margin: 0, maxWidth: wide ? 620 : 'none' }}>{sub}</p>
    </Reveal>
  </div>;


// ---- product chrome, values lifted from Figma Screen/Create·desktop ----
const AppTopBar = () =>
<div style={{ height: 46, background: 'rgb(252,248,248)', borderBottom: '1px solid rgba(28,27,27,0.08)',
  display: 'flex', alignItems: 'center', gap: 18, padding: '0 16px', flexShrink: 0 }}>
    <img loading="lazy" decoding="async" src="assets/logo-icon.png" alt="" style={{ width: 20, height: 20, objectFit: 'contain' }} />
    <div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
      {['Explore', 'Create', 'Characters', 'Vault'].map((l, i) =>
    <span key={l} style={{ fontSize: 11, fontWeight: i === 1 ? 700 : 500, color: i === 1 ? 'rgb(5,5,5)' : 'rgba(28,27,27,0.6)' }}>{l}</span>
    )}
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
        <span style={{ fontSize: 11, fontWeight: 500, color: 'rgb(5,5,5)' }}>Scheduler</span>
        <span style={{ fontSize: 8.5, fontWeight: 700, color: '#fff', background: 'rgb(129,60,237)', borderRadius: 9999, padding: '2px 6px' }}>New</span>
      </span>
    </div>
    <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 10 }}>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'rgb(235,231,231)', borderRadius: 9999, padding: '5px 10px' }}>
        <span style={{ fontSize: 10, color: 'rgb(245,165,36)' }}>⚡</span>
        <span style={{ fontSize: 10.5, fontWeight: 700, color: 'rgb(5,5,5)' }}>9.38k</span>
      </span>
      <span style={{ width: 24, height: 24, borderRadius: '50%', background: 'rgb(115,140,235)', display: 'grid', placeItems: 'center',
      fontSize: 10, fontWeight: 700, color: '#fff' }}>J</span>
    </div>
  </div>;


const AppWindow = ({ children, style }) =>
<div style={{ borderRadius: 20, overflow: 'hidden', background: '#fff',
  boxShadow: '0 20px 40px rgba(28,27,27,0.10), inset 0 0 0 1px rgba(28,27,27,0.08)', ...style }}>
    {children}
  </div>;


const ControlPill = ({ children, style }) =>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, borderRadius: 9999,
  background: 'rgb(246,243,242)', boxShadow: 'inset 0 0 0 1px rgba(28,27,27,0.08)',
  padding: '9px 14px', fontSize: 12, fontWeight: 500, color: 'rgb(28,27,27)', whiteSpace: 'nowrap', ...style }}>
    {children}<span style={{ color: 'rgba(28,27,27,0.55)', fontSize: 10 }}>⌄</span>
  </span>;


const GenBtn = ({ active = false, count = 1, style }) =>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, borderRadius: 14, padding: '0 18px', height: 46,
  background: active ? PULSE : 'rgb(235,231,231)',
  boxShadow: active ? '0 8px 20px -4px rgba(104,19,212,0.45)' : 'none',
  transition: `all 0.4s ${SB_EASE}`, cursor: 'pointer', flexShrink: 0, ...style }}>
    <span style={{ fontSize: 12.5, fontWeight: 700, color: active ? '#fff' : 'rgba(28,27,27,0.6)', transition: 'color 0.4s' }}>Generate ✦</span>
    <span style={{ width: 20, height: 20, borderRadius: 9999, background: active ? 'rgba(255,255,255,0.25)' : 'rgb(129,60,237)',
    display: 'grid', placeItems: 'center', fontSize: 10.5, fontWeight: 600, color: '#fff' }}>{count}</span>
  </span>;


const Shimmer = ({ style }) =>
<div style={{ background: 'linear-gradient(90deg, rgba(129,60,237,0.08) 25%, rgba(129,60,237,0.18) 50%, rgba(129,60,237,0.08) 75%)',
  backgroundSize: '200% 100%', animation: 'sz-shimmer 1.4s linear infinite', ...style }} />;


const Waveform = ({ on = true, bars = 24, color = 'var(--brand-purple-500)', h = 26 }) =>
<div style={{ display: 'flex', alignItems: 'center', gap: 2.5, height: h }}>
    {Array.from({ length: bars }).map((_, i) =>
  <span key={i} style={{ width: 2.5, height: h * (0.3 + i * 37 % 10 / 14), borderRadius: 2, background: color,
    transformOrigin: 'center', animation: on ? `sz-wave ${0.9 + i % 5 * 0.14}s ease-in-out ${i * 0.05}s infinite` : 'none' }} />
  )}
  </div>;


const SITE_W = 1140;
const SectionShell = ({ children, dark = false, pad = '110px 56px', bg }) =>
<section style={{ background: bg || (dark ? '#141017' : CREAM), padding: pad }}>
    <div style={{ maxWidth: SITE_W, margin: '0 auto' }}>{children}</div>
  </section>;


Object.assign(window, { useReveal, Reveal, useCountUp, useTyping, useScrollProgress, useSmoothScroll, SectionHead, AppTopBar, AppWindow, ControlPill, GenBtn, Shimmer, Waveform, SectionShell, SB_EASE, SITE_W });
// Perf: pause any autoplay video while offscreen; resume when visible again.
(function szVideoGovernor() {
  if (window.__szVidGov) return; window.__szVidGov = 1;
  const io = new IntersectionObserver((ents) => {
    ents.forEach((en) => {
      const v = en.target;
      if (en.isIntersecting) { if (v.paused) v.play().catch(() => {}); }
      else if (!v.paused) v.pause();
    });
  }, { rootMargin: '120px' });
  const scan = () => document.querySelectorAll('video[autoplay]').forEach((v) => {
    if (!v.__szGov) { v.__szGov = 1; io.observe(v); }
  });
  const mo = new MutationObserver(scan);
  mo.observe(document.body, { childList: true, subtree: true });
  scan();
})();


// ---------- § 02 — Character builder (the live 10-step setup wizard) ----------
// Option tiles are crops of the real app's setup screens (assets/builder/*).
const CB_KEYS = ['skin', 'eye', 'hair', 'length', 'texture', 'lips', 'chest', 'hips', 'butt', 'thighs'];
const cbSlug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-');
const cbOpts = (key, names) => names.map(n => [n, `assets/builder/${key}-${cbSlug(n)}.png`]);
const CB_WIZ = [
  { t: 'Skin tone', s: 'Choose the skin tone for your character.', sel: 1,
    o: cbOpts('skin', ['Fair', 'Light', 'Medium', 'Olive', 'Tan', 'Brown', 'Dark', 'Black']) },
  { t: 'Eye color', s: 'Select the eye color for your character.', sel: 6,
    o: cbOpts('eye', ['Brown', 'Dark brown', 'Hazel', 'Green', 'Blue', 'Gray', 'Amber']) },
  { t: 'Hair color', s: 'Choose the hair color for your character.', sel: 6,
    o: cbOpts('hair', ['Black', 'Dark brown', 'Brown', 'Light brown', 'Blonde', 'Platinum blonde', 'Red', 'Auburn', 'Silver', 'Pink']) },
  { t: 'Hair length', s: 'Choose the hair length for your character.', sel: 1,
    o: cbOpts('length', ['Buzzed', 'Short', 'Shoulder-length', 'Long', 'Very long']) },
  { t: 'Hair texture', s: 'Choose the hair texture for your character.', sel: 2,
    o: cbOpts('texture', ['Straight', 'Wavy', 'Curly', 'Coily']) },
  { t: 'Lips', s: 'Choose the lip shape for your character.', sel: 2,
    o: cbOpts('lips', ['Thin', 'Medium', 'Full', 'Very full']) },
  { t: 'Breast size', s: 'Choose the closest body shape for your character.', sel: 1,
    o: cbOpts('chest', ['Flat', 'Small', 'Medium', 'Large', 'Extremely large']) },
  { t: 'Hips', s: 'Choose the hip shape for your character.', sel: 2,
    o: cbOpts('hips', ['Narrow', 'Medium', 'Wide', 'Extremely wide']) },
  { t: 'Butt', s: 'Choose the butt shape for your character.', sel: 2,
    o: cbOpts('butt', ['Small', 'Average', 'Round', 'Large', 'Very large']) },
  { t: 'Thighs', s: 'Choose the thigh shape for your character.', sel: 2,
    o: cbOpts('thighs', ['Slim', 'Average', 'Toned', 'Thick', 'Very thick']) },
];

const CBTile = ({ name, src, on, small, onClick }) => (
  <div onClick={onClick} style={{ position: 'relative', borderRadius: small ? 9 : 12, overflow: 'hidden', aspectRatio: small ? '1' : '3/3.5', cursor: 'pointer',
    background: 'linear-gradient(180deg, rgb(232,228,227), rgb(208,203,201))',
    boxShadow: on ? '0 0 0 2px var(--brand-purple-500)' : 'inset 0 0 0 1px rgba(28,27,27,0.08)', transition: 'box-shadow 0.18s' }}>
    <img loading="lazy" decoding="async" src={src} alt="" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top' }} />
    <span style={{ position: 'absolute', inset: 0, background: 'linear-gradient(transparent 58%, rgba(0,0,0,0.45))' }} />
    <span style={{ position: 'absolute', left: small ? 5 : 8, bottom: small ? 4 : 7, right: 4, fontSize: small ? 7.5 : 10, fontWeight: 700, color: '#fff',
      whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</span>
    {on && <span style={{ position: 'absolute', top: 6, right: 6, width: 16, height: 16, borderRadius: '50%', background: 'var(--brand-purple-600)', display: 'grid', placeItems: 'center' }}>
      <svg width="9" height="9" viewBox="0 0 16 16" fill="none"><path d="M3 8.4l3.2 3.2L13.5 4" stroke="#fff" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round" /></svg></span>}
  </div>
);

Object.assign(window, { CB_WIZ, CBTile });
