// lantic-ui.jsx — Shared primitives for the Lantic marketing site.
// Brand canon, Cortex orb, FeedCard, Wordmark, scroll hooks, founder line-art.
// Exposed on window for the main marketing-site script.

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

// ─────────────────────────────────────────────────────────────────
// Brand tokens (from Lantic RC Handoff canon)
// ─────────────────────────────────────────────────────────────────
const BRAND = {
  canvas:     '#0F172A',
  canvasDeep: '#080C18',
  ink:        '#F8FAFC',
  sub:        'rgba(248,250,252,.72)',
  dim:        'rgba(248,250,252,.46)',
  faint:      'rgba(248,250,252,.20)',
  hair:       'rgba(248,250,252,.08)',
  card:       'rgba(255,255,255,.04)',
  cardBorder: 'rgba(255,255,255,.09)',
  sky:        '#0EA5E9',
  skyDeep:    '#0284C7',
  skyDark:    '#075985',
  skyLight:   '#38BDF8',
  skyMist:    '#BAE6FD',
  accent:     '#38BDF8',
  good:       '#10B981',
  warn:       '#FB923C',
};

const LOBES = {
  work:   { id: 'work',   label: 'WORK',   col: '#0284C7' },
  money:  { id: 'money',  label: 'MONEY',  col: '#10B981' },
  family: { id: 'family', label: 'FAMILY', col: '#FB923C' },
  team:   { id: 'team',   label: 'TEAM',   col: '#7DD3FC' },
  goals:  { id: 'goals',  label: 'GOALS',  col: '#A78BFA' },
  notes:  { id: 'notes',  label: 'NOTES',  col: '#FBBF24' },
  health: { id: 'health', label: 'HEALTH', col: '#F472B6' },
};
const LOBE_ORDER = ['work','money','family','team','goals','notes','health'];

const SAT  = '"Satoshi", -apple-system, "SF Pro Display", system-ui, sans-serif';
const SF   = '-apple-system, BlinkMacSystemFont, "SF Pro Display", system-ui, sans-serif';
const MONO = 'ui-monospace, "SF Mono", "JetBrains Mono", monospace';

// ─────────────────────────────────────────────────────────────────
// One-time keyframes + global glyph CSS
// ─────────────────────────────────────────────────────────────────
if (typeof document !== 'undefined' && !document.getElementById('lm-kf')) {
  const s = document.createElement('style'); s.id = 'lm-kf';
  s.textContent = `
    @keyframes lm-breath { 0%,100%{transform:scale(1)} 50%{transform:scale(1.022)} }
    @keyframes lm-glow   { 0%,100%{opacity:.55} 50%{opacity:.85} }
    @keyframes lm-spin   { 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} }
    @keyframes lm-rise   { 0%{opacity:0;transform:translateY(14px)} 100%{opacity:1;transform:translateY(0)} }
    @keyframes lm-fade   { 0%{opacity:0} 100%{opacity:1} }
    @keyframes lm-caret  { 0%,49%{opacity:1} 50%,100%{opacity:0} }
    @keyframes lm-ring   { 0%{transform:scale(.85);opacity:.9} 100%{transform:scale(2.4);opacity:0} }
    @keyframes lm-dot    { 0%,80%,100%{opacity:.3;transform:translateY(0)} 40%{opacity:1;transform:translateY(-2px)} }
    @keyframes lm-orbit  { 0%{transform:rotate(0deg)} 100%{transform:rotate(360deg)} }
    @media (prefers-reduced-motion: reduce) {
      .lm-rm-soft { animation-duration: 16s !important; }
      .lm-rm-off  { animation: none !important; transform: none !important; }
    }
  `;
  document.head.appendChild(s);
}

// ─────────────────────────────────────────────────────────────────
// Hooks
// ─────────────────────────────────────────────────────────────────
function useReducedMotion() {
  const [r, setR] = useState(false);
  useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const up = () => setR(mq.matches); up();
    mq.addEventListener('change', up); return () => mq.removeEventListener('change', up);
  }, []); return r;
}

// Smooth scroll progress through an element. 0 when top edge enters viewport
// bottom; 1 when bottom edge leaves viewport top.
function useElementProgress(ref) {
  const [p, setP] = useState(0);
  useEffect(() => {
    let raf = 0;
    const update = () => {
      const el = ref.current; if (!el) return;
      const rect = el.getBoundingClientRect();
      const vh = window.innerHeight || 1;
      const total = rect.height + vh;
      const passed = vh - rect.top;
      setP(Math.max(0, Math.min(1, total > 0 ? passed / total : 0)));
    };
    const onScroll = () => { if (raf) return; raf = requestAnimationFrame(()=>{ raf = 0; update(); }); };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    update();
    return () => { window.removeEventListener('scroll', onScroll); window.removeEventListener('resize', onScroll); cancelAnimationFrame(raf); };
  }, [ref]); return p;
}

// In-view boolean for one-shot entry animations.
function useInView(ref, threshold = 0.1) {
  // Initialize with sync rect check so first render isn't blank when JS is fast and
  // the element is already on screen (e.g. hero on page load).
  const [v, setV] = useState(() => {
    if (typeof window === 'undefined') return false;
    return false;
  });
  useLayoutEffect(() => {
    const el = ref.current; if (!el) return;
    if (v) return;
    const check = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight || 1;
      if (r.top < vh * (1 - threshold) && r.bottom > vh * threshold) {
        setV(true);
        return true;
      }
      return false;
    };
    if (check()) return;
    let raf = 0;
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { raf = 0; if (check()) cleanup(); });
    };
    const cleanup = () => {
      window.removeEventListener('scroll', onScroll);
      window.removeEventListener('resize', onScroll);
      cancelAnimationFrame(raf);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    window.addEventListener('resize', onScroll);
    let io;
    try {
      io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setV(true); cleanup(); io?.disconnect(); } }, { threshold });
      io.observe(el);
    } catch (e) {}
    const t1 = setTimeout(() => check(), 80);
    const t2 = setTimeout(() => check(), 400);
    const t3 = setTimeout(() => check(), 1200);
    return () => { cleanup(); io?.disconnect(); clearTimeout(t1); clearTimeout(t2); clearTimeout(t3); };
  }, [ref, threshold, v]);
  return v;
}

// Viewport size — used to switch desktop/mobile layout decisions.
function useViewport() {
  const [w, setW] = useState(() => typeof window !== 'undefined' ? window.innerWidth : 1024);
  useEffect(() => {
    const on = () => setW(window.innerWidth);
    window.addEventListener('resize', on); return () => window.removeEventListener('resize', on);
  }, []); return w;
}

// ─────────────────────────────────────────────────────────────────
// Math + easing
// ─────────────────────────────────────────────────────────────────
const clamp01 = v => Math.max(0, Math.min(1, v));
const easeOutCubic   = t => 1 - Math.pow(1 - t, 3);
const easeInOutCubic = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t + 2, 3) / 2;
const easeOutExpo    = t => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);

// Map p across keyframes [in0,in1,...] → [out0,out1,...] with optional easing
function map(p, inR, outR, easing) {
  if (p <= inR[0]) return outR[0];
  if (p >= inR[inR.length-1]) return outR[outR.length-1];
  for (let i = 0; i < inR.length-1; i++) {
    if (p >= inR[i] && p <= inR[i+1]) {
      let t = (p - inR[i]) / (inR[i+1] - inR[i]);
      if (easing) t = easing(t);
      const a = outR[i], b = outR[i+1];
      return typeof a === 'number' ? a + (b - a) * t : b;
    }
  }
  return outR[outR.length-1];
}

// ─────────────────────────────────────────────────────────────────
// Cortex orb — 220-node Fibonacci sphere, k-NN edges. Canon §3.
// Spins ambient at 55s/rev (CSS), parallax-rotates via `rotateY` prop only
// when an external scroll wants to override it.
// ─────────────────────────────────────────────────────────────────
const CORTEX_LATTICE = (() => {
  let seed = 101;
  const rnd = () => { seed = (seed * 9301 + 49297) % 233280; return seed / 233280; };
  const count = 220, R = 44;
  const nodes = [];
  for (let i = 0; i < count; i++) {
    const t = i / count;
    const phi = Math.acos(1 - 2*t);
    const theta = Math.PI * (1 + Math.sqrt(5)) * i;
    const bias = Math.sin(theta*3 + phi*5) * 0.06;
    nodes.push({
      i,
      x: Math.sin(phi+bias) * Math.cos(theta) * R,
      y: Math.sin(phi+bias) * Math.sin(theta) * R,
      z: Math.cos(phi+bias) * R,
      depth: (Math.cos(phi+bias) + 1) / 2,
      bright: rnd() > 0.88,
    });
  }
  const edges = []; const seen = new Set();
  for (const n of nodes) {
    const others = nodes.map(o => ({ o, d: Math.hypot(n.x-o.x, n.y-o.y, n.z-o.z) }))
      .filter(p => p.o !== n).sort((a,b)=>a.d-b.d).slice(0, 4);
    for (const p of others) {
      const k = n.i < p.o.i ? `${n.i}-${p.o.i}` : `${p.o.i}-${n.i}`;
      if (seen.has(k)) continue;
      seen.add(k);
      edges.push({ a: n, b: p.o, id: k });
    }
  }
  // Recenter the node cloud so its centroid sits exactly at (0,0). The Fibonacci
  // distribution + bias perturbation leaves a small offset; without this the orb
  // appears to drift off the centered glow circle as it spins.
  const mx = nodes.reduce((s,n)=>s+n.x,0) / nodes.length;
  const my = nodes.reduce((s,n)=>s+n.y,0) / nodes.length;
  for (const n of nodes) { n.x -= mx; n.y -= my; }
  return { nodes, edges };
})();

function Cortex({
  size = 220, tint = BRAND.skyLight, mist = BRAND.skyMist,
  glowLobe = null, glowAlpha = 0.6,
  animate = true,
  satisfied = false,
  glow = true,
}) {
  const { nodes, edges } = CORTEX_LATTICE;
  const reduced = useReducedMotion();
  const breathing = animate && !reduced;
  const spinning  = animate && !reduced;

  // Satisfied state = gentler, warmer tint (after waitlist submit)
  const _tint = satisfied ? '#7DD3FC' : tint;
  const _mist = satisfied ? '#E0F2FE' : mist;

  return (
    <div style={{
      width: size, height: size, position: 'relative',
      animation: breathing ? 'lm-breath 5s ease-in-out infinite' : 'none',
      willChange: 'transform',
    }}>
      {/* Outer glow */}
      {glow && (
      <div style={{
        position: 'absolute', inset: -size*0.35, borderRadius: '50%',
        background: `radial-gradient(circle, ${_tint}55 0%, ${_tint}22 35%, transparent 65%)`,
        filter: 'blur(10px)',
        animation: breathing ? 'lm-glow 6s ease-in-out infinite' : 'none',
        pointerEvents: 'none',
      }}/>
      )}
      {/* Lobe-specific glow (pulses) */}
      {glow && glowLobe && (
        <div style={{
          position: 'absolute', inset: -size*0.32, borderRadius: '50%',
          background: `radial-gradient(circle at 50% 50%, ${glowLobe} 0%, transparent 50%)`,
          mixBlendMode: 'screen', filter: 'blur(14px)',
          opacity: glowAlpha,
          transition: 'opacity 320ms cubic-bezier(.4,0,.2,1)',
          pointerEvents: 'none',
        }}/>
      )}
      {/* Spinning lattice */}
      <div style={{
        position: 'absolute', inset: 0,
        animation: spinning ? 'lm-spin 55s linear infinite' : 'none',
      }}>
        <svg width={size} height={size} viewBox="-50 -50 100 100" style={{ overflow: 'visible' }}>
          {edges.map(e => (
            <line key={'e'+e.id} x1={e.a.x} y1={e.a.y} x2={e.b.x} y2={e.b.y}
              stroke={_mist} strokeWidth="0.14"
              opacity={0.10 + ((e.a.depth + e.b.depth)/2) * 0.30}/>
          ))}
          {nodes.map((n, i) => (
            <circle key={'n'+i} cx={n.x} cy={n.y}
              r={0.45 + n.depth*0.7 + (n.bright ? 0.5 : 0)}
              fill={n.bright ? '#fff' : _mist}
              opacity={0.4 + n.depth*0.5}
              style={n.bright ? { filter: `drop-shadow(0 0 2px ${_tint})` } : null}/>
          ))}
        </svg>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Wordmark — Satoshi 700, locked spec (canon)
// ─────────────────────────────────────────────────────────────────
function Wordmark({ size = 26, color = BRAND.ink, opacity = 1 }) {
  // Tracking scales linearly with size around -1.6/em at 48px reference.
  const trackEm = -0.033; // ~ -1.6 at 48px
  return (
    <span style={{
      fontFamily: SAT, fontWeight: 700, fontSize: size, color, opacity,
      letterSpacing: `${trackEm}em`, lineHeight: 1, display: 'inline-block',
    }}>Lantic</span>
  );
}

// Eyebrow — mono, narrow caps, wide tracking (canon)
function Eyebrow({ children, color = BRAND.dim, size = 11 }) {
  return <div style={{
    fontFamily: MONO, fontSize: size, color,
    letterSpacing: '0.14em', fontWeight: 500, textTransform: 'uppercase',
  }}>{children}</div>;
}

// ─────────────────────────────────────────────────────────────────
// Phone frame — used in Sec 2 (cards) + Sec 4 (Day with Lantic)
// ─────────────────────────────────────────────────────────────────
function PhoneFrame({ width = 320, height = 660, children, glow = false }) {
  return (
    <div style={{
      width, height, position: 'relative', borderRadius: 44,
      background: BRAND.canvas,
      boxShadow: `
        0 0 0 1px rgba(255,255,255,.06),
        0 0 0 8px #0c1320,
        0 0 0 9px rgba(255,255,255,.04),
        0 30px 80px -10px rgba(0,0,0,.6),
        ${glow ? `0 0 80px ${BRAND.skyDark}55` : '0 8px 20px rgba(0,0,0,.4)'}
      `,
      overflow: 'hidden',
    }}>
      {/* Notch */}
      <div style={{ position: 'absolute', top: 10, left: '50%', transform: 'translateX(-50%)',
        width: 96, height: 26, borderRadius: 14, background: '#000', zIndex: 5 }}/>
      {/* Status bar */}
      <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 44,
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        padding: '14px 28px 0', fontSize: 13, fontWeight: 600, color: BRAND.ink, zIndex: 4 }}>
        <span>9:41</span>
        <span style={{ display: 'inline-flex', gap: 5, opacity: 0.85, fontSize: 11 }}>
          <span>●●●</span><span>▾</span><span>▮</span>
        </span>
      </div>
      <div style={{ position: 'absolute', inset: 0, paddingTop: 44 }}>
        {children}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// FeedCard — single Briefing entry. Matches rc-day2day.jsx pattern.
// ─────────────────────────────────────────────────────────────────
function FeedCard({ tag, color, time, body, cta, compact = false }) {
  return (
    <div style={{
      padding: compact ? 12 : 14, borderRadius: 14,
      background: BRAND.card, border: `1px solid ${BRAND.cardBorder}`,
      backdropFilter: 'blur(10px)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <div style={{ width: 5, height: 5, borderRadius: 3, background: color,
            boxShadow: `0 0 6px ${color}` }}/>
          <div style={{ fontFamily: MONO, fontSize: 10, color, letterSpacing: '0.16em', fontWeight: 600 }}>{tag}</div>
        </div>
        {time && <div style={{ fontFamily: MONO, fontSize: 10.5, color: BRAND.dim }}>{time}</div>}
      </div>
      <div style={{ fontSize: 13.5, color: BRAND.ink, lineHeight: 1.45, letterSpacing: -0.1, textWrap: 'pretty' }}>
        {body}
      </div>
      {cta && (
        <div style={{ marginTop: 10, display: 'flex', gap: 6 }}>
          <button style={{
            height: 28, padding: '0 12px', borderRadius: 14, border: 'none',
            background: `${color}1f`, color, fontSize: 12, fontWeight: 600, cursor: 'pointer',
          }}>{cta}</button>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// FounderLineArt — Apple-illustration style single-stroke figure.
//   variant: 'lineart' (default) | 'abstract' | 'geometric' | 'none'
// ─────────────────────────────────────────────────────────────────
function FounderLineArt({ variant = 'lineart', size = 360, stroke = BRAND.skyMist, opacity = 0.65 }) {
  if (variant === 'none') return null;
  if (variant === 'abstract') {
    return (
      <div style={{ width: size, height: size, position: 'relative' }}>
        <div style={{ position: 'absolute', inset: '20%', borderRadius: '50%',
          background: `radial-gradient(ellipse at 50% 35%, ${BRAND.skyDark}66 0%, transparent 65%)`,
          filter: 'blur(20px)', opacity }}/>
        <div style={{ position: 'absolute', inset: '38%', borderRadius: '50%',
          background: `radial-gradient(circle, ${BRAND.skyLight}44 0%, transparent 70%)`,
          filter: 'blur(8px)' }}/>
      </div>
    );
  }
  if (variant === 'geometric') {
    return (
      <svg width={size} height={size} viewBox="0 0 200 200" style={{ opacity, overflow: 'visible' }}>
        {/* back of head */}
        <path d="M 70 50 Q 70 22 100 22 Q 130 22 130 55 L 130 90 Q 130 100 122 102 L 100 108 L 78 102 Q 70 100 70 90 Z"
          fill={BRAND.canvasDeep} stroke={stroke} strokeWidth="1.2"/>
        {/* shoulder plane */}
        <path d="M 38 200 L 50 130 Q 70 110 100 110 Q 130 110 150 130 L 162 200 Z"
          fill={BRAND.canvasDeep} stroke={stroke} strokeWidth="1.2"/>
        {/* neck */}
        <path d="M 84 108 L 88 122 L 112 122 L 116 108" fill="none" stroke={stroke} strokeWidth="1.2"/>
      </svg>
    );
  }
  // 'lineart' — Apple illustration single-stroke, 3/4 view facing right
  return (
    <svg width={size} height={size} viewBox="0 0 200 240" style={{ opacity, overflow: 'visible' }}>
      <defs>
        <linearGradient id="laGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor={stroke} stopOpacity="0.95"/>
          <stop offset="1" stopColor={stroke} stopOpacity="0.25"/>
        </linearGradient>
      </defs>
      <g fill="none" stroke="url(#laGrad)" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round">
        {/* Cranium back curve */}
        <path d="M 84 38 C 64 42 56 64 60 88 C 62 102 70 112 80 116"/>
        {/* Forehead → bridge → nose */}
        <path d="M 84 38 C 100 32 122 38 130 56 C 134 66 132 76 128 84 C 132 88 134 96 130 102 C 128 106 124 108 122 108"/>
        {/* Jaw/chin sweep */}
        <path d="M 122 108 C 124 116 122 124 116 130 C 110 134 102 134 96 132"/>
        {/* Neck */}
        <path d="M 96 132 C 92 142 90 150 90 158"/>
        {/* Back of neck */}
        <path d="M 80 116 C 80 128 78 140 76 150"/>
        {/* Shoulders → arms */}
        <path d="M 76 150 C 56 156 40 168 32 196 L 32 240"/>
        <path d="M 90 158 C 96 168 108 174 128 174 C 152 174 170 188 178 220 L 178 240"/>
        {/* Inner shoulder line */}
        <path d="M 90 158 C 90 172 92 188 96 200"/>
        {/* Subtle hair line over crown */}
        <path d="M 82 42 C 96 30 116 32 128 44" opacity="0.6"/>
      </g>
    </svg>
  );
}

// ─────────────────────────────────────────────────────────────────
// Tiny visual primitives
// ─────────────────────────────────────────────────────────────────
function Hair({ color = BRAND.hair, vertical = false, length = '100%' }) {
  return <div style={{
    background: color, [vertical ? 'width' : 'height']: 1,
    [vertical ? 'height' : 'width']: length,
  }}/>;
}

function ScrollHint({ label = 'SCROLL', color = BRAND.dim }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
      animation: 'lm-fade 1s ease 1.2s both' }}>
      <div style={{ fontFamily: MONO, fontSize: 10, color, letterSpacing: '0.3em', fontWeight: 500 }}>{label}</div>
      <div style={{ width: 1, height: 36, background: `linear-gradient(to bottom, ${color}, transparent)`,
        animation: 'lm-glow 2.4s ease-in-out infinite' }}/>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Export to window
// ─────────────────────────────────────────────────────────────────
Object.assign(window, {
  // tokens
  BRAND, LOBES, LOBE_ORDER, SAT, SF, MONO,
  // hooks
  useReducedMotion, useElementProgress, useInView, useViewport,
  // math
  clamp01, easeOutCubic, easeInOutCubic, easeOutExpo, map,
  // components
  Cortex, Wordmark, Eyebrow, PhoneFrame, FeedCard, FounderLineArt, Hair, ScrollHint,
});
