// motion.jsx — small motion primitives used everywhere.
// - useCountUp: number animates from start → end over duration
// - <Wiggle>: triggers a one-shot wiggle when its `trigger` prop changes
// - <FadeIn>: mount-time fade-up
// - <Breathing>: applies the breathing animation
// - useTickRipple: helper for showing a brief "celebrate" state then resetting

const __MOTION_CSS = `
@keyframes m-wiggle  { 0%,100%{transform:rotate(0) scale(1)} 25%{transform:rotate(-4deg) scale(1.04)} 75%{transform:rotate(4deg) scale(1.04)} }
@keyframes m-pop     { 0%{transform:scale(.6);opacity:0} 60%{transform:scale(1.1);opacity:1} 100%{transform:scale(1);opacity:1} }
@keyframes m-fadeup  { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
@keyframes m-breathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.02)} }
@keyframes m-glow    { 0%,100%{box-shadow:0 0 0 0 rgba(46,107,74,0)} 50%{box-shadow:0 0 0 8px rgba(46,107,74,0.15)} }
@keyframes m-shimmer { 0%{background-position:-150% 0} 100%{background-position:250% 0} }
.m-wiggle  { animation: m-wiggle 0.55s cubic-bezier(.36,.07,.19,.97); transform-origin: center; }
.m-pop     { animation: m-pop 0.32s cubic-bezier(.5,1.6,.4,1); }
.m-fadeup  { animation: m-fadeup 0.32s ease-out both; }
.m-breathe { animation: m-breathe 3s ease-in-out infinite; }
.m-glow    { animation: m-glow 1.6s ease-in-out infinite; }
`;

if (typeof document !== 'undefined' && !document.getElementById('motion-css')) {
  const s = document.createElement('style');
  s.id = 'motion-css';
  s.textContent = __MOTION_CSS;
  document.head.appendChild(s);
}

// useCountUp — counts to `to` whenever `to` changes. RAF-based.
function useCountUp(to, { duration = 700, decimals = 0 } = {}) {
  const [v, setV] = React.useState(to);
  const startRef = React.useRef({ from: to, to, t: 0 });
  React.useEffect(() => {
    const from = startRef.current.to;
    startRef.current = { from, to, t: performance.now() };
    let raf;
    const step = (now) => {
      const elapsed = now - startRef.current.t;
      const p = Math.min(1, elapsed / duration);
      // ease-out cubic
      const e = 1 - Math.pow(1 - p, 3);
      const next = from + (to - from) * e;
      setV(decimals === 0 ? Math.round(next) : Number(next.toFixed(decimals)));
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [to, duration, decimals]);
  return v;
}

// Wiggle — applies the wiggle keyframe whenever `trigger` changes.
function Wiggle({ trigger, children, style }) {
  const ref = React.useRef(null);
  const firstRender = React.useRef(true);
  React.useEffect(() => {
    if (firstRender.current) { firstRender.current = false; return; }
    const el = ref.current;
    if (!el) return;
    el.classList.remove('m-wiggle');
    void el.offsetWidth;
    el.classList.add('m-wiggle');
  }, [trigger]);
  return <div ref={ref} style={{ display: 'inline-block', ...style }}>{children}</div>;
}

function Pop({ trigger, children, style }) {
  const ref = React.useRef(null);
  const firstRender = React.useRef(true);
  React.useEffect(() => {
    if (firstRender.current) { firstRender.current = false; return; }
    const el = ref.current;
    if (!el) return;
    el.classList.remove('m-pop');
    void el.offsetWidth;
    el.classList.add('m-pop');
  }, [trigger]);
  return <div ref={ref} style={{ display: 'inline-block', ...style }}>{children}</div>;
}

function FadeIn({ children, delay = 0, style }) {
  return (
    <div className="m-fadeup" style={{ animationDelay: `${delay}ms`, ...style }}>{children}</div>
  );
}

// Tooltip — long-press / click anchor with content.
function Tooltip({ text, c, children }) {
  const [open, setOpen] = React.useState(false);
  React.useEffect(() => {
    if (!open) return;
    const close = () => setOpen(false);
    window.addEventListener('click', close);
    const t = setTimeout(close, 3500);
    return () => { window.removeEventListener('click', close); clearTimeout(t); };
  }, [open]);
  return (
    <span style={{ position: 'relative', display: 'inline-flex' }}>
      <button onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        aria-label="More info"
        style={{ width: 18, height: 18, border: 'none', borderRadius: 9, background: c.surfaceAlt, color: c.textMuted, fontSize: 11, fontWeight: 700, cursor: 'pointer', padding: 0, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
        i
      </button>
      {open && (
        <div style={{
          position: 'absolute', bottom: 'calc(100% + 8px)', left: '50%', transform: 'translateX(-50%)',
          background: c.text, color: '#fff', borderRadius: 10, padding: '8px 12px',
          fontSize: 12, fontWeight: 500, lineHeight: 1.4, minWidth: 180, maxWidth: 240,
          textAlign: 'center', boxShadow: '0 8px 24px rgba(0,0,0,0.18)', zIndex: 999,
          animation: 'm-fadeup .15s ease-out',
        }}>
          {text}
          <div style={{ position: 'absolute', bottom: -4, left: '50%', transform: 'translateX(-50%) rotate(45deg)', width: 8, height: 8, background: c.text }} />
        </div>
      )}
    </span>
  );
}

// Odometer — rolling-digit number display, like an energy meter / flip clock.
// Each digit column slides vertically to its target; when `value` changes the
// affected columns roll. `value` is a number; `decimals` fixes precision.
// `prefix` (e.g. "$") renders static ahead of the digits.
// Odometer — analog rolling-digit meter. The least-significant wheel rolls
// CONTINUOUSLY with the true sub-digit value (at 2.665 the last wheel sits
// halfway between 6 and 7); higher wheels only roll during the final 10% before
// their carry, exactly like a mechanical energy meter. Driven by the real
// continuous `value` — it moves only as fast as the value actually changes.
function Odometer({ value, decimals = 2, prefix = '', fontSize = 38, color, weight = 700, digitStyle, framed = false }) {
  const v = Math.max(0, value);
  const factor = Math.pow(10, decimals);
  const scaledLSB = v * factor;                 // value in least-significant-wheel units
  // Truncated string drives the LAYOUT (digit count + decimal position) so it
  // never rounds 2.999→3.00; rolling positions come from the continuous value.
  const truncated = Math.floor(scaledLSB) / factor;
  const str = truncated.toFixed(decimals);
  const chars = str.split('');
  const digitH = fontSize * 1.12;
  const w = fontSize * 0.6;
  const box = { display: 'inline-block', height: digitH, lineHeight: `${digitH}px`, verticalAlign: 'bottom', fontSize };

  // Count digits to the right of each position so we know each wheel's place.
  const totalDigits = chars.filter(ch => ch !== '.').length;
  let digitsSeen = 0;

  return (
    <span style={{ display: 'inline-flex', alignItems: 'flex-end', fontVariantNumeric: 'tabular-nums', color, fontWeight: weight, letterSpacing: -0.5 }}>
      {prefix && <span style={{ ...box, textAlign: 'center' }}>{prefix}</span>}
      {chars.map((ch, i) => {
        if (ch === '.') {
          return <span key={`dot${i}`} style={{ ...box, width: w * 0.42, textAlign: 'center' }}>.</span>;
        }
        // place power from least-significant displayed digit (k=0 is last)
        const k = (totalDigits - 1) - digitsSeen;
        digitsSeen += 1;
        const wv = scaledLSB / Math.pow(10, k);
        const floorWv = Math.floor(wv);
        const base = ((floorWv % 10) + 10) % 10;
        const frac = wv - floorWv;
        // Only the least-significant wheel rolls continuously; higher wheels
        // snap to their floor digit (carry roll would sit mid-digit when
        // draining, which reads as a sliced half-digit).
        const pos = k === 0 ? base + frac : base;
        return (
          <span key={i} style={{ ...box, overflow: 'hidden', position: 'relative', width: w,
            ...(framed ? { background: 'rgba(255,255,255,0.06)', borderRadius: 5, boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)', margin: '0 0.5px' } : {}),
            ...digitStyle }}>
            <span style={{
              display: 'block', position: 'absolute', left: 0, right: 0, top: 0,
              transform: `translateY(${-pos * digitH}px)`,
              transition: 'transform .25s linear',
              textAlign: 'center',
            }}>
              {/* 0..9 then a trailing 0 so a rolling 9 wraps smoothly into 0 */}
              {[0,1,2,3,4,5,6,7,8,9,0].map((n, ni) => (
                <span key={ni} style={{ display: 'block', height: digitH, lineHeight: `${digitH}px`, fontSize }}>{n}</span>
              ))}
            </span>
          </span>
        );
      })}
    </span>
  );
}

Object.assign(window, { useCountUp, Odometer, Wiggle, Pop, FadeIn, Tooltip });
