// primitives.jsx
// Shared visual primitives for the Terracode video.

const T = {
  orange: '#f56c04',
  orangeBright: '#ff8a2e',
  orangeDeep: '#c14a00',
  black: '#0a0a0a',
  ink: '#050505',
  white: '#ffffff',
  bone: '#f6f4ef',
  red: '#e63a3a',
  gold: '#d9a441',
  goldGlow: '#f7c75a',
  mute: 'rgba(255,255,255,0.55)',
  faint: 'rgba(255,255,255,0.10)',
  display: '"Space Grotesk", "Inter", system-ui, sans-serif',
  body: '"Inter", system-ui, sans-serif',
  mono: '"JetBrains Mono", ui-monospace, monospace',
};

// ── Backdrop ────────────────────────────────────────────────────────────────
// Subtle grain + radial vignette + faint grid lines for the "dark tech" feel.
function Backdrop({ accent = T.orange, tone = 'deep' }) {
  const time = useTime();
  // very slow parallax drift
  const drift = (time * 6) % 80;
  const glow = tone === 'deep'
    ? `radial-gradient(120% 80% at 50% 40%, rgba(245,108,4,0.10) 0%, rgba(245,108,4,0.02) 35%, rgba(10,10,10,1) 70%)`
    : `radial-gradient(120% 80% at 50% 30%, rgba(245,108,4,0.18) 0%, rgba(245,108,4,0.04) 40%, rgba(10,10,10,1) 75%)`;
  return (
    <>
      <div style={{
        position: 'absolute', inset: 0,
        background: '#0a0a0a',
      }} />
      {/* glow */}
      <div style={{
        position: 'absolute', inset: 0,
        background: glow,
      }} />
      {/* grid lines */}
      <div style={{
        position: 'absolute', inset: -40,
        backgroundImage:
          'linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.035) 1px, transparent 1px)',
        backgroundSize: '80px 80px',
        backgroundPosition: `${drift}px ${drift * 0.5}px`,
        maskImage: 'radial-gradient(75% 80% at 50% 50%, #000 30%, transparent 80%)',
        WebkitMaskImage: 'radial-gradient(75% 80% at 50% 50%, #000 30%, transparent 80%)',
      }} />
      {/* grain */}
      <div style={{
        position: 'absolute', inset: 0,
        opacity: 0.06,
        mixBlendMode: 'overlay',
        backgroundImage:
          "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='0.9'/></svg>\")",
      }} />
    </>
  );
}

// ── Logo ────────────────────────────────────────────────────────────────────
// TERRA (white) CODE. (orange). The period is the focal mark.
function TerracodeLogo({ size = 56, pulse = false, glow = false }) {
  const { localTime, duration } = useSprite();
  // pulse: subtle scale breath
  const breath = pulse ? 1 + 0.02 * Math.sin(localTime * Math.PI * 2 / 1.2) : 1;
  const dotOpacity = pulse ? 0.6 + 0.4 * Math.abs(Math.sin(localTime * Math.PI * 2 / 1.2)) : 1;

  return (
    <div style={{
      display: 'inline-flex',
      alignItems: 'baseline',
      fontFamily: T.display,
      fontWeight: 700,
      fontSize: size,
      letterSpacing: '-0.03em',
      transform: `scale(${breath})`,
      transformOrigin: 'center',
      filter: glow ? `drop-shadow(0 0 24px rgba(245,108,4,0.35))` : 'none',
      whiteSpace: 'nowrap',
    }}>
      <span style={{ color: T.white }}>TERRA</span>
      <span style={{ color: T.orange }}>CODE</span>
      <span style={{ color: T.orange, opacity: dotOpacity }}>.</span>
    </div>
  );
}

// ── Animated orange line that cuts across screen ────────────────────────────
function OrangeLine({ start = 0, end = 1, y = '50%', thickness = 3, easing = Easing.easeOutExpo, glowStrength = 1 }) {
  const { localTime, duration } = useSprite();
  const cutEnd = Math.min(0.5, duration * 0.25);
  const t = easing(clamp(localTime / cutEnd, 0, 1));
  const w = t * 100;
  return (
    <div style={{
      position: 'absolute',
      left: '50%', top: y,
      width: `${w}%`,
      height: thickness,
      transform: 'translate(-50%, -50%)',
      background: `linear-gradient(90deg, transparent, ${T.orange} 20%, ${T.orangeBright} 50%, ${T.orange} 80%, transparent)`,
      boxShadow: `0 0 ${24 * glowStrength}px rgba(245,108,4,${0.7 * glowStrength}), 0 0 ${60 * glowStrength}px rgba(245,108,4,${0.3 * glowStrength})`,
      borderRadius: thickness,
    }} />
  );
}

// ── Particle burst (radial dots) ────────────────────────────────────────────
function ParticleField({ count = 60, hue = T.orange }) {
  const { localTime, duration } = useSprite();
  // memo'd seeds
  const seeds = React.useMemo(() => Array.from({ length: count }, (_, i) => {
    const angle = (i / count) * Math.PI * 2 + (i % 7) * 0.13;
    const speed = 200 + ((i * 37) % 260);
    const size = 1 + ((i * 11) % 4);
    const phase = (i % 11) / 11;
    return { angle, speed, size, phase };
  }), [count]);

  return (
    <div style={{
      position: 'absolute', inset: 0,
      pointerEvents: 'none',
    }}>
      {seeds.map((s, i) => {
        const cycle = 3.5;
        const tt = ((localTime + s.phase * cycle) % cycle) / cycle;
        const eased = Easing.easeOutCubic(tt);
        const dist = eased * s.speed;
        const opacity = (1 - tt) * 0.9;
        const x = Math.cos(s.angle) * dist;
        const y = Math.sin(s.angle) * dist;
        return (
          <div key={i} style={{
            position: 'absolute',
            left: '50%', top: '50%',
            width: s.size, height: s.size,
            marginLeft: -s.size / 2, marginTop: -s.size / 2,
            borderRadius: s.size,
            background: hue,
            opacity,
            transform: `translate(${x}px, ${y}px)`,
            boxShadow: `0 0 6px ${hue}`,
          }} />
        );
      })}
    </div>
  );
}

// ── Scene wipe overlay ──────────────────────────────────────────────────────
// An orange band that sweeps across to bridge between scenes.
function OrangeWipe({ direction = 'right' }) {
  const { localTime, duration } = useSprite();
  // wipe enters in first 45%, exits in last 45%
  const t = clamp(localTime / duration, 0, 1);
  let x;
  if (t < 0.5) {
    x = interpolate([0, 0.5], [-110, 0], Easing.easeInQuart)(t);
  } else {
    x = interpolate([0.5, 1], [0, 110], Easing.easeOutQuart)(t);
  }
  const sign = direction === 'right' ? 1 : -1;
  return (
    <div style={{
      position: 'absolute', inset: 0,
      overflow: 'hidden',
      pointerEvents: 'none',
    }}>
      <div style={{
        position: 'absolute',
        top: 0, bottom: 0,
        left: `${x * sign}%`,
        width: '140%',
        background: `linear-gradient(90deg, transparent 0%, ${T.orange} 25%, ${T.orangeBright} 50%, ${T.orange} 75%, transparent 100%)`,
        boxShadow: `0 0 80px rgba(245,108,4,0.6)`,
        transform: 'skewX(-12deg)',
      }} />
    </div>
  );
}

// ── Reveal text word-by-word ────────────────────────────────────────────────
function WordReveal({
  words = [],
  start = 0,
  stagger = 0.12,
  perWordDur = 0.35,
  size = 64,
  color = T.white,
  font = T.display,
  weight = 700,
  highlightIdx = [],
  lineGap = 4,
  align = 'left',
  letterSpacing = '-0.02em',
}) {
  const { localTime } = useSprite();
  return (
    <div style={{
      display: 'flex', flexWrap: 'wrap', gap: `${lineGap}px 14px`,
      fontFamily: font, fontWeight: weight, fontSize: size,
      color, letterSpacing, lineHeight: 1.05,
      justifyContent: align === 'center' ? 'center' : align === 'right' ? 'flex-end' : 'flex-start',
    }}>
      {words.map((w, i) => {
        const wStart = start + i * stagger;
        const local = localTime - wStart;
        const t = clamp(local / perWordDur, 0, 1);
        const eased = Easing.easeOutBack(t);
        const opacity = clamp(local / (perWordDur * 0.7), 0, 1);
        const isHi = highlightIdx.includes(i);
        return (
          <span key={i} style={{
            display: 'inline-block',
            transform: `translateY(${(1 - eased) * 18}px)`,
            opacity,
            color: isHi ? T.orange : color,
            textShadow: isHi ? `0 0 24px rgba(245,108,4,0.55)` : 'none',
            willChange: 'transform, opacity',
          }}>{w}</span>
        );
      })}
    </div>
  );
}

// ── Slam-in headline ────────────────────────────────────────────────────────
function SlamHeadline({ children, start = 0, dur = 0.5, size = 96, color = T.white, font = T.display, weight = 700, letterSpacing = '-0.03em', maxWidth = 940, align = 'left' }) {
  const { localTime } = useSprite();
  const local = localTime - start;
  const t = clamp(local / dur, 0, 1);
  const eased = Easing.easeOutExpo(t);
  // slight blur on entry
  const blur = (1 - eased) * 12;
  // shake on impact
  const impact = local > dur && local < dur + 0.12
    ? Math.sin((local - dur) * 80) * (1 - (local - dur) / 0.12) * 4
    : 0;
  return (
    <div style={{
      maxWidth,
      transform: `translateY(${(1 - eased) * 30}px) translateX(${impact}px)`,
      opacity: t,
      filter: `blur(${blur}px)`,
      fontFamily: font, fontWeight: weight, fontSize: size,
      color, letterSpacing, lineHeight: 1.03,
      textAlign: align,
      willChange: 'transform, opacity, filter',
    }}>{children}</div>
  );
}

// ── Stat counter ────────────────────────────────────────────────────────────
function StatCounter({ value = 15, suffix = '+', label = '', start = 0, dur = 1.2, size = 140, x = 0, y = 0 }) {
  const { localTime } = useSprite();
  const t = clamp((localTime - start) / dur, 0, 1);
  const eased = Easing.easeOutCubic(t);
  const display = Math.floor(eased * value);
  const opacity = clamp((localTime - start) / 0.3, 0, 1);
  return (
    <div style={{
      position: 'absolute', left: x, top: y,
      transform: 'translateX(-50%)',
      opacity,
      textAlign: 'center',
      fontFamily: T.display,
    }}>
      <div style={{
        fontSize: size,
        fontWeight: 700,
        color: T.white,
        letterSpacing: '-0.04em',
        lineHeight: 1,
        display: 'flex',
        alignItems: 'baseline',
        justifyContent: 'center',
        gap: 4,
      }}>
        <span style={{ fontVariantNumeric: 'tabular-nums' }}>{display}</span>
        <span style={{ color: T.orange, fontSize: size * 0.7 }}>{suffix}</span>
      </div>
      <div style={{
        marginTop: 6,
        fontFamily: T.mono,
        fontSize: 22,
        textTransform: 'uppercase',
        letterSpacing: '0.18em',
        color: T.mute,
      }}>{label}</div>
    </div>
  );
}

// ── Service tile ────────────────────────────────────────────────────────────
function ServiceTile({ icon, label, start = 0, x = 0, y = 0, width = 460, height = 220 }) {
  const { localTime } = useSprite();
  const t = clamp((localTime - start) / 0.5, 0, 1);
  const eased = Easing.easeOutCubic(t);
  return (
    <div style={{
      position: 'absolute', left: x, top: y,
      width, height,
      transform: `translateY(${(1 - eased) * 40}px)`,
      opacity: t,
      background: 'linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.01))',
      border: `1px solid rgba(255,255,255,0.08)`,
      borderRadius: 18,
      padding: '28px 30px',
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'space-between',
      backdropFilter: 'blur(6px)',
    }}>
      <div style={{
        width: 52, height: 52,
        borderRadius: 12,
        background: 'rgba(245,108,4,0.12)',
        border: `1px solid rgba(245,108,4,0.35)`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        color: T.orange,
      }}>
        {icon}
      </div>
      <div style={{
        fontFamily: T.display,
        fontSize: 32,
        fontWeight: 600,
        color: T.white,
        letterSpacing: '-0.01em',
        lineHeight: 1.1,
      }}>{label}</div>
    </div>
  );
}

// ── Pain item row (with X icon) ─────────────────────────────────────────────
function PainItem({ label, start = 0, y = 0, shatterAt = Infinity }) {
  const { localTime } = useSprite();
  const t = clamp((localTime - start) / 0.45, 0, 1);
  const eased = Easing.easeOutCubic(t);

  // shatter / glitch out
  const sLocal = localTime - shatterAt;
  let shatterOpacity = 1;
  let shatterTx = 0;
  let shatterScale = 1;
  let glitchSkew = 0;
  if (sLocal > 0) {
    const st = clamp(sLocal / 0.5, 0, 1);
    shatterOpacity = 1 - st;
    shatterTx = st * 80 * ((Math.sin(start * 13) > 0) ? 1 : -1);
    shatterScale = 1 - st * 0.05;
    glitchSkew = Math.sin(sLocal * 80) * st * 8;
  }

  return (
    <div style={{
      position: 'absolute',
      left: 80, right: 80,
      top: y,
      transform: `translateX(${(1 - eased) * -40 + shatterTx}px) scale(${shatterScale}) skewX(${glitchSkew}deg)`,
      opacity: t * shatterOpacity,
      display: 'flex',
      alignItems: 'center',
      gap: 28,
      fontFamily: T.display,
      fontSize: 50,
      fontWeight: 500,
      color: T.white,
      letterSpacing: '-0.01em',
      lineHeight: 1.1,
      willChange: 'transform, opacity',
    }}>
      <div style={{
        width: 64, height: 64,
        borderRadius: 16,
        background: 'rgba(230,58,58,0.15)',
        border: `1.5px solid ${T.red}`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        flexShrink: 0,
        boxShadow: `0 0 24px rgba(230,58,58,0.35)`,
      }}>
        <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
          <path d="M6 6L18 18M18 6L6 18" stroke={T.red} strokeWidth="3" strokeLinecap="round"/>
        </svg>
      </div>
      <div>{label}</div>
    </div>
  );
}

// ── Price reveal: crossed-out → new price slides in glowing ─────────────────
function PriceReveal({ oldPrice, newPrice, suffix = '/mo', start = 0, x = 0, y = 0, big = false }) {
  const { localTime } = useSprite();
  // old price fades in at start, crossout at start+0.5, new slides in at start+0.9
  const oldOp = clamp((localTime - start) / 0.35, 0, 1);
  const crossT = clamp((localTime - (start + 0.45)) / 0.35, 0, 1);
  const newT = clamp((localTime - (start + 0.85)) / 0.45, 0, 1);
  const oldFade = 1 - clamp((localTime - (start + 1.1)) / 0.4, 0, 1) * 0.5; // fade to 50%
  return (
    <div style={{
      position: 'absolute', left: x, top: y,
      display: 'flex', alignItems: 'baseline', gap: 16,
    }}>
      {/* old price (strike-through) */}
      <div style={{
        position: 'relative',
        fontFamily: T.display,
        fontSize: big ? 44 : 36,
        fontWeight: 500,
        color: 'rgba(255,255,255,0.45)',
        opacity: oldOp * oldFade,
        letterSpacing: '-0.01em',
      }}>
        <span>{oldPrice}</span>
        <div style={{
          position: 'absolute',
          left: -4, right: -4,
          top: '52%',
          height: 2,
          background: T.red,
          transformOrigin: 'left center',
          transform: `scaleX(${crossT})`,
          boxShadow: `0 0 8px ${T.red}`,
        }}/>
      </div>
      {/* new price */}
      <div style={{
        fontFamily: T.display,
        fontSize: big ? 72 : 56,
        fontWeight: 700,
        color: T.orange,
        letterSpacing: '-0.025em',
        opacity: newT,
        transform: `translateY(${(1 - Easing.easeOutBack(newT)) * 14}px)`,
        textShadow: `0 0 ${28 * newT}px rgba(245,108,4,0.6)`,
        lineHeight: 1,
        display: 'inline-flex', alignItems: 'baseline', gap: 4,
      }}>
        <span>{newPrice}</span>
        <span style={{ color: T.mute, fontSize: big ? 22 : 18, fontFamily: T.mono, letterSpacing: '0.05em' }}>{suffix}</span>
      </div>
    </div>
  );
}

// ── Service icons (simple geometric) ────────────────────────────────────────
const Icons = {
  software: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <rect x="3" y="4" width="18" height="14" rx="2" stroke="currentColor" strokeWidth="2"/>
      <path d="M3 9h18" stroke="currentColor" strokeWidth="2"/>
      <circle cx="6" cy="6.5" r="0.7" fill="currentColor"/>
    </svg>
  ),
  ai: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2"/>
      <path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
    </svg>
  ),
  web: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2"/>
      <path d="M3 12h18M12 3c2.5 3 2.5 15 0 18M12 3c-2.5 3-2.5 15 0 18" stroke="currentColor" strokeWidth="2"/>
    </svg>
  ),
  mobile: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <rect x="7" y="2" width="10" height="20" rx="2" stroke="currentColor" strokeWidth="2"/>
      <circle cx="12" cy="18" r="1" fill="currentColor"/>
    </svg>
  ),
  cloud: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <path d="M7 18a4 4 0 010-8 5 5 0 019.6-1.3A4 4 0 0117 18H7z" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"/>
    </svg>
  ),
  ux: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <path d="M4 4l8 16 2-7 7-2L4 4z" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"/>
    </svg>
  ),
  shield: (
    <svg width="28" height="28" viewBox="0 0 24 24" fill="none">
      <path d="M12 3l8 3v6c0 4.5-3.5 8-8 9-4.5-1-8-4.5-8-9V6l8-3z" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"/>
      <path d="M9 12l2 2 4-4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  ),
};

// ── LocalSprite ─────────────────────────────────────────────────────────────
// Like <Sprite>, but reads time from the PARENT sprite's local clock (via
// useSprite) instead of the global timeline. Lets you nest scene-relative
// sub-timings inside a scene wrapper without doing offset math.
function LocalSprite({ start = 0, end = Infinity, children, keepMounted = false }) {
  const parent = useSprite();
  const parentLocal = parent.localTime;
  const visible = parentLocal >= start && parentLocal <= end;
  if (!visible && !keepMounted) return null;

  const duration = end - start;
  const localTime = Math.max(0, parentLocal - start);
  const progress = duration > 0 && isFinite(duration)
    ? clamp(localTime / duration, 0, 1)
    : 0;

  const value = { localTime, progress, duration, visible };

  return (
    <SpriteContext.Provider value={value}>
      {typeof children === 'function' ? children(value) : children}
    </SpriteContext.Provider>
  );
}

Object.assign(window, {
  T, Backdrop, TerracodeLogo, OrangeLine, ParticleField,
  OrangeWipe, WordReveal, SlamHeadline, StatCounter,
  ServiceTile, PainItem, PriceReveal, Icons, LocalSprite,
});
