// components/landing-orb.jsx
// ─────────────────────────────────────────────────────────────
// LandingOrb — the signature "reply just landed" moment.
//
// The central Mahana logo sits still. Model marks orbit around it while
// the council is thinking, then sling-shot into the logo the instant the
// reply lands — collapsing a cloud of N model contributions into one
// unified Mahana voice. This is the visual thesis of the product.
//
// Lifted from mastermind-v2/components/landing-orb.jsx with these
// improvements for the playground + reuse contexts:
//   - `confidences` now renders below the orb as a compact legend
//     (the v2 component accepted the prop but silently dropped it)
//   - Extracted inner `OrbStage` so the orb can be used headless
//     (without the column wrapper) — e.g. inline in a message row.
//   - `ConfidenceRow` exported separately for callers that want the
//     legend without the orb (post-reply verdict bar, etc.).
//   - Respect prefers-reduced-motion: collapse instantly, no spin.
//   - `logoSrc` default points at the project's copy of mahana-logo.png.
// ─────────────────────────────────────────────────────────────

/* global React, MODELS_BY_ID */

const { useEffect: _loUseEffect, useState: _loUseState, useRef: _loUseRef } = React;

function _prefersReducedMotion() {
  if (typeof window === 'undefined' || !window.matchMedia) return false;
  return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}

// ─────────────────────────────────────────────────────────────
// OrbStage — the animated orb itself, no column, no legend.
// Use this when embedding inline (e.g. next to a message avatar).
// ─────────────────────────────────────────────────────────────
function OrbStage({
  voices,
  size = 96,
  state = 'landed',           // 'thinking' | 'landing' | 'landed'
  logoSrc = '../assets/mahana-logo.png',
}) {
  if (!voices || voices.length === 0) return null;
  const N = voices.length;
  const radius = size * 0.45;
  const reduce = _prefersReducedMotion();

  // progress: 0 = orbiting far out, 1 = collapsed into the center
  const [progress, setProgress] = _loUseState(state === 'thinking' ? 0 : 1);
  const [spin, setSpin] = _loUseState(0);
  const rafRef = _loUseRef();

  const target = state === 'thinking' ? 0 : 1;

  // Animate to target
  _loUseEffect(() => {
    if (reduce) { setProgress(target); return; }
    let start = null;
    const from = progress;
    const delta = target - from;
    const dur = state === 'landing' ? 1200 : 400;
    function tick(t) {
      if (start == null) start = t;
      const e = Math.min(1, (t - start) / dur);
      const eased = 1 - Math.pow(1 - e, 3); // easeOutCubic
      setProgress(from + delta * eased);
      if (e < 1) rafRef.current = requestAnimationFrame(tick);
    }
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
    // eslint-disable-next-line
  }, [target]);

  // Slow spin while thinking
  _loUseEffect(() => {
    if (state !== 'thinking' || reduce) return;
    let raf;
    let last = performance.now();
    function loop(t) {
      const dt = t - last; last = t;
      setSpin(s => (s + dt * 0.05) % 360);
      raf = requestAnimationFrame(loop);
    }
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [state, reduce]);

  const haloColors = voices.map(v => MODELS_BY_ID[v.modelId]?.color || '#999').join(', ');
  const firstColor = MODELS_BY_ID[voices[0].modelId]?.color || '#999';

  return (
    <div style={{
      position: 'relative', width: size, height: size,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      overflow: 'visible',
    }}>
      {/* conic halo — lit while thinking, fades in landing, dim while landed */}
      <div style={{
        position: 'absolute',
        width: size * (1.1 + progress * 0.08),
        height: size * (1.1 + progress * 0.08),
        borderRadius: '50%',
        background: `conic-gradient(from ${spin}deg, ${haloColors}, ${firstColor})`,
        opacity: state === 'thinking' ? 0.45 : 0.12,
        filter: `blur(${10 + progress * 4}px)`,
        transition: 'opacity 240ms',
      }} />

      {/* central Mahana logo */}
      <div style={{
        width: size, height: size, borderRadius: '50%',
        overflow: 'hidden',
        boxShadow: '0 4px 14px rgba(0, 0, 0, 0.06), 0 0 0 1.5px rgba(255,255,255,0.95) inset',
        background: '#fff',
        position: 'relative',
        transform: `scale(${1 + progress * 0.03})`,
        transition: 'transform 120ms',
      }}>
        <img src={logoSrc} alt="Mahana"
          style={{
            width: '100%', height: '100%', objectFit: 'cover',
            transform: `scale(${1.02 + progress * 0.02})`,
            filter: progress < 1 ? `saturate(${0.6 + progress * 0.4})` : 'none',
          }} />
        {/* glass highlight */}
        <div style={{
          position: 'absolute', inset: 0, borderRadius: '50%',
          background: 'radial-gradient(circle at 30% 25%, rgba(255,255,255,0.55), rgba(255,255,255,0) 55%)',
          pointerEvents: 'none',
        }} />
      </div>

      {/* orbiting → landing model marks */}
      {voices.map((v, i) => {
        const m = MODELS_BY_ID[v.modelId];
        if (!m) return null;
        const baseAngle = (360 / N) * i - 90;
        const angle = baseAngle + (state === 'thinking' ? spin : 0);
        // r interpolates from orbit → center of logo
        const r = radius * (1 - progress) + (size * 0.32) * progress;
        const x = Math.cos(angle * Math.PI / 180) * r;
        const y = Math.sin(angle * Math.PI / 180) * r;
        const chipSize = 26 * (1 - progress * 0.55);
        // fade into logo once landed
        const opacity = progress < 0.6 ? 1 : Math.max(0, 1 - (progress - 0.6) * 2.6);
        return (
          <div key={v.modelId + '-' + i} style={{
            position: 'absolute',
            left: '50%', top: '50%',
            transform: `translate(${x - chipSize / 2}px, ${y - chipSize / 2}px)`,
            opacity: Math.max(0, opacity),
            pointerEvents: 'none',
          }}>
            <div style={{
              width: chipSize, height: chipSize, borderRadius: '50%',
              background: '#fff',
              boxShadow: `0 2px 6px rgba(0,0,0,0.12), 0 0 0 2px ${m.color}40`,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              overflow: 'hidden',
            }}>
              <div style={{
                width: '70%', height: '70%',
                backgroundColor: m.color,
                WebkitMaskImage: `url(${m.icon})`, maskImage: `url(${m.icon})`,
                WebkitMaskSize: 'contain', maskSize: 'contain',
                WebkitMaskRepeat: 'no-repeat', maskRepeat: 'no-repeat',
                WebkitMaskPosition: 'center', maskPosition: 'center',
              }} />
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// ConfidenceRow — model pill • percent, separated by middots.
// Used as the default legend below the orb, but also reusable on
// its own (e.g. inline in a VerdictBar).
// ─────────────────────────────────────────────────────────────
function ConfidenceRow({ confidences, compact = false }) {
  if (!confidences || confidences.length === 0) return null;
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: compact ? 8 : 12,
      flexWrap: 'wrap', justifyContent: 'center',
      fontFamily: window.MM_FONT || 'var(--pg-font)',
      fontSize: compact ? 11 : 12,
      color: 'rgba(0,0,0,0.62)',
    }}>
      {confidences.map((c, i) => {
        const m = MODELS_BY_ID[c.modelId];
        if (!m) return null;
        return (
          <span key={c.modelId + '-' + i}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <span style={{
              width: compact ? 8 : 10, height: compact ? 8 : 10,
              borderRadius: '50%', background: m.color, flexShrink: 0,
              boxShadow: `0 0 0 1px ${m.color}40`,
            }} />
            <span style={{ color: 'rgba(0,0,0,0.82)', fontWeight: 500 }}>{m.name}</span>
            <span style={{ fontVariantNumeric: 'tabular-nums' }}>{c.pct}%</span>
          </span>
        );
      })}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// LandingOrb — the full composition: orb + confidence legend.
// This is the default export for most callers.
// ─────────────────────────────────────────────────────────────
function LandingOrb({
  voices,
  size = 96,
  state = 'landed',
  confidences,
  logoSrc,
  showLegend = true,
}) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14 }}>
      <OrbStage voices={voices} size={size} state={state} logoSrc={logoSrc} />
      {showLegend && confidences && <ConfidenceRow confidences={confidences} />}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// LandingOrbDemo — a toggleable demo (Replay button on hover).
// Used inline in a chat message to let the user watch the landing
// animation again. This is what the canonical app renders.
// ─────────────────────────────────────────────────────────────
function LandingOrbDemo({ voices, confidences, size = 96, autoPlay = false }) {
  const [phase, setPhase] = _loUseState(autoPlay ? 'thinking' : 'landed');
  const [key, setKey] = _loUseState(0);
  const [hover, setHover] = _loUseState(false);

  _loUseEffect(() => {
    if (!autoPlay) return;
    const t1 = setTimeout(() => setPhase('landing'), 900);
    const t2 = setTimeout(() => setPhase('landed'), 2200);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [key, autoPlay]);

  const replay = () => {
    setKey(k => k + 1);
    setPhase('thinking');
    setTimeout(() => setPhase('landing'), 900);
    setTimeout(() => setPhase('landed'), 2200);
  };

  return (
    <div style={{ position: 'relative', display: 'inline-block' }}
         onMouseEnter={() => setHover(true)}
         onMouseLeave={() => setHover(false)}>
      <LandingOrb key={key} voices={voices} confidences={confidences}
                  state={phase} size={size} />
      <button onClick={replay} title="Replay landing" style={{
        position: 'absolute',
        top: size - 8, right: -4,
        width: 22, height: 22, padding: 0,
        borderRadius: '50%',
        background: '#fff',
        border: '1px solid rgba(0,0,0,0.08)',
        boxShadow: '0 2px 6px rgba(0,0,0,0.08)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        fontFamily: window.MM_FONT || 'var(--pg-font)', fontSize: 11, color: 'rgba(0,0,0,0.55)',
        cursor: 'pointer',
        opacity: hover ? 1 : 0,
        transform: hover ? 'scale(1)' : 'scale(0.85)',
        transition: 'opacity 150ms, transform 150ms',
      }}>↻</button>
    </div>
  );
}

Object.assign(window, { LandingOrb, LandingOrbDemo, OrbStage, ConfidenceRow });
