// Motion system — tokens, hooks, primitives
// Inspired by Apple's Human Interface Motion + Framer's spring physics.
// Principles:
//   1. Motion reinforces meaning, never decorates.
//   2. Exits are faster than entries (users want things to get out of the way).
//   3. Origin-aware: things animate FROM where they came from.
//   4. Interruptible: everything can be canceled mid-flight.

const MOTION = {
  // Durations (ms)
  dur: {
    instant: 80,
    fast: 140,
    base: 220,
    slow: 360,
    slower: 520,
  },
  // Easings — custom cubic-beziers tuned for UI
  ease: {
    // Standard — most transitions
    standard: 'cubic-bezier(0.2, 0.8, 0.2, 1)',
    // Decelerate — for entries (starts fast, settles gently)
    out: 'cubic-bezier(0.16, 1, 0.3, 1)',
    // Accelerate — for exits (leaves decisively)
    in: 'cubic-bezier(0.7, 0, 0.84, 0)',
    // Both — for things that move through
    inOut: 'cubic-bezier(0.65, 0, 0.35, 1)',
    // Spring-y — for playful, bouncy moments
    spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
    // Linear (only for loading loops)
    linear: 'linear',
  },
  // Spring presets (for hand-rolled JS-driven springs)
  spring: {
    gentle:  { stiffness: 170, damping: 26, mass: 1 },
    wobbly:  { stiffness: 220, damping: 14, mass: 1 },
    stiff:   { stiffness: 320, damping: 30, mass: 1 },
    slow:    { stiffness: 120, damping: 28, mass: 1 },
  },
  // Stagger (for list entries)
  stagger: {
    tight: 20,
    base:  40,
    loose: 80,
  },
};

// Hook: useReducedMotion — respect OS setting
const useReducedMotion = () => {
  const [reduced, setReduced] = React.useState(
    typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches
  );
  React.useEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const onChange = () => setReduced(mq.matches);
    mq.addEventListener?.('change', onChange);
    return () => mq.removeEventListener?.('change', onChange);
  }, []);
  return reduced;
};

// Hook: useMounted — `true` after first paint so CSS transitions fire on mount
const useMounted = (delay = 0) => {
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => {
    const r = requestAnimationFrame(() =>
      delay ? setTimeout(() => setMounted(true), delay) : setMounted(true)
    );
    return () => cancelAnimationFrame(r);
  }, []);
  return mounted;
};

// Hook: useDelayUnmount — keep component mounted during exit animation
const useDelayUnmount = (isOpen, delay = MOTION.dur.base) => {
  const [render, setRender] = React.useState(isOpen);
  React.useEffect(() => {
    if (isOpen) setRender(true);
    else {
      const t = setTimeout(() => setRender(false), delay);
      return () => clearTimeout(t);
    }
  }, [isOpen, delay]);
  return render;
};

// <FadeSlide> — generic entry wrapper. from = 'bottom' | 'top' | 'left' | 'right' | 'scale' | 'fade'
const FadeSlide = ({ children, from = 'bottom', delay = 0, distance = 8, duration, easing, style = {}, ...rest }) => {
  const reduced = useReducedMotion();
  const mounted = useMounted(delay);
  const d = reduced ? 0 : (duration ?? MOTION.dur.base);
  const e = easing || MOTION.ease.out;
  const offsets = {
    bottom: `translate3d(0, ${distance}px, 0)`,
    top:    `translate3d(0, -${distance}px, 0)`,
    left:   `translate3d(-${distance}px, 0, 0)`,
    right:  `translate3d(${distance}px, 0, 0)`,
    scale:  'scale(0.96)',
    fade:   'none',
  };
  return (
    <div style={{
      opacity: mounted ? 1 : 0,
      transform: mounted ? 'none' : offsets[from],
      transition: `opacity ${d}ms ${e}, transform ${d}ms ${e}`,
      willChange: 'opacity, transform',
      ...style,
    }} {...rest}>{children}</div>
  );
};

// <Stagger> — wraps children so each gets a delay
const Stagger = ({ children, delay = MOTION.stagger.base, start = 0, from = 'bottom', ...rest }) => {
  const kids = React.Children.toArray(children);
  return <>{kids.map((c, i) => (
    <FadeSlide key={i} delay={start + i * delay} from={from} {...rest}>{c}</FadeSlide>
  ))}</>;
};

// <Pressable> — button-like wrapper with push-down effect
const Pressable = ({ children, onClick, scale = 0.96, style = {}, as = 'div', ...rest }) => {
  const [pressed, setPressed] = React.useState(false);
  const Tag = as;
  return (
    <Tag
      onMouseDown={() => setPressed(true)}
      onMouseUp={() => setPressed(false)}
      onMouseLeave={() => setPressed(false)}
      onTouchStart={() => setPressed(true)}
      onTouchEnd={() => setPressed(false)}
      onClick={onClick}
      style={{
        transform: pressed ? `scale(${scale})` : 'scale(1)',
        transition: `transform ${pressed ? 80 : 200}ms ${pressed ? MOTION.ease.in : MOTION.ease.spring}`,
        cursor: 'pointer', userSelect: 'none',
        display: 'inline-block',
        ...style,
      }}
      {...rest}
    >{children}</Tag>
  );
};

// <AnimateNumber> — smoothly counts to target
const AnimateNumber = ({ value, duration = 900, format = v => v.toLocaleString(), style = {} }) => {
  const [display, setDisplay] = React.useState(value);
  const prev = React.useRef(value);
  React.useEffect(() => {
    const start = prev.current;
    const end = value;
    if (start === end) return;
    const t0 = performance.now();
    let raf;
    const tick = t => {
      const p = Math.min(1, (t - t0) / duration);
      // Ease out quint
      const eased = 1 - Math.pow(1 - p, 5);
      setDisplay(Math.round(start + (end - start) * eased));
      if (p < 1) raf = requestAnimationFrame(tick);
      else prev.current = end;
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [value, duration]);
  return <span style={{ fontVariantNumeric: 'tabular-nums', ...style }}>{format(display)}</span>;
};

// <Ripple> — click ripple effect (Material-ish but tuned)
const Ripple = ({ color = 'currentColor', opacity = 0.15 }) => {
  const [ripples, setRipples] = React.useState([]);
  const ref = React.useRef(null);

  React.useEffect(() => {
    const el = ref.current?.parentElement;
    if (!el) return;
    const handler = e => {
      const r = el.getBoundingClientRect();
      const x = e.clientX - r.left;
      const y = e.clientY - r.top;
      const size = Math.max(r.width, r.height) * 2;
      const id = Date.now() + Math.random();
      setRipples(rr => [...rr, { id, x, y, size }]);
      setTimeout(() => setRipples(rr => rr.filter(rp => rp.id !== id)), 600);
    };
    el.addEventListener('click', handler);
    return () => el.removeEventListener('click', handler);
  }, []);

  return (
    <span ref={ref} style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none', borderRadius: 'inherit' }}>
      {ripples.map(r => (
        <span key={r.id} style={{
          position: 'absolute',
          left: r.x - r.size / 2, top: r.y - r.size / 2,
          width: r.size, height: r.size, borderRadius: '50%',
          background: color, opacity,
          animation: 'ripple 550ms ease-out forwards',
          pointerEvents: 'none',
        }}/>
      ))}
    </span>
  );
};

// <HoverLift> — card hover that lifts + brightens
const HoverLift = ({ children, lift = 2, style = {}, ...rest }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <div
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        transform: hover ? `translateY(-${lift}px)` : 'translateY(0)',
        boxShadow: hover ? 'var(--shadow-lg)' : 'var(--shadow-sm)',
        transition: `transform 220ms ${MOTION.ease.out}, box-shadow 220ms ${MOTION.ease.out}`,
        ...style,
      }}
      {...rest}
    >{children}</div>
  );
};

// <Marquee> — infinite horizontal scroll (for activity tickers)
const Marquee = ({ children, speed = 40, style = {} }) => (
  <div style={{ overflow: 'hidden', display: 'flex', ...style }}>
    <div style={{
      display: 'flex', gap: 40, flexShrink: 0,
      animation: `marquee ${speed}s linear infinite`,
    }}>
      {children}{children}
    </div>
  </div>
);

// Global keyframes injected once
(function injectKeyframes() {
  if (document.getElementById('__motion-keyframes')) return;
  const s = document.createElement('style');
  s.id = '__motion-keyframes';
  s.textContent = `
    @keyframes ripple { from { transform: scale(0); opacity: 0.4; } to { transform: scale(1); opacity: 0; } }
    @keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
    @keyframes spin { to { transform: rotate(360deg); } }
    @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
    @keyframes shimmer { 0% { background-position: -200px 0; } 100% { background-position: 200px 0; } }
    @keyframes slideInRight { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
    @keyframes slideInBottom { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
    @keyframes scaleIn { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }
    @keyframes confetti-fall {
      0% { transform: translateY(-20px) rotate(0deg); opacity: 1; }
      100% { transform: translateY(400px) rotate(720deg); opacity: 0; }
    }
    @keyframes check-draw {
      from { stroke-dashoffset: 30; }
      to { stroke-dashoffset: 0; }
    }
    .lift-on-hover { transition: transform 180ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 180ms cubic-bezier(0.16, 1, 0.3, 1); }
    .lift-on-hover:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
    .btn { position: relative; overflow: hidden; }
    .btn:hover { filter: brightness(0.96); }
    .btn:active { transform: scale(0.98); }
  `;
  document.head.appendChild(s);
})();

Object.assign(window, {
  MOTION, useReducedMotion, useMounted, useDelayUnmount,
  FadeSlide, Stagger, Pressable, AnimateNumber, Ripple, HoverLift, Marquee,
});
