/**
 * embeddings-projector.jsx — 3D PCA-projected embedding viewer.
 *
 * Ported from /Users/sanjaymehta/code/delectable/demo/public-giant-eagle/
 * eagle-ai-dev-console/src/pages/CognitionPage.tsx (lines 983-1282).
 *
 * Loads pre-projected 3D points from public-shared/data/embeddings-seed.json
 * (or whatever URL is set as window.EMBEDDING_SEED_URL). No browser-side
 * projection — the seed file holds points already collapsed to 3D via PCA
 * (server-side in prod via grocery-agent/routes.py:7227).
 *
 * Interactions: orbit (rotate/zoom/pan), hover (raycaster), click-to-select.
 * Source dropdown, text filter, color legend. No nearest-neighbor edges.
 *
 * Exposed on window.EmbeddingsProjector — the dashboard mounts it via:
 *   {activeTab === 'embeddingsProjector' && window.EmbeddingsProjector && <window.EmbeddingsProjector />}
 *
 * Loaded as a <script type="text/babel"> sibling to data-insights-dashboard.jsx,
 * so React + JSX are already available. three.js + OrbitControls come from
 * an importmap declared in data-insights-dashboard.html.
 */
(function () {
  'use strict';
  const { useEffect, useState, useRef, useCallback, useMemo } = React;

  const CATEGORY_PALETTE = [
    '#5eead4', // teal-300 — Produce
    '#a78bfa', // violet-400 — Dairy
    '#fbbf24', // amber-400 — Bakery
    '#f87171', // red-400 — Meat & Seafood
    '#34d399', // emerald-400 — Pantry
    '#60a5fa', // blue-400 — Beverages
    '#c4b5fd', // violet-300 — Frozen
    '#f472b6', // pink-400 — Snacks & Sweets
    '#94a3b8', // slate-400 — Other
    '#fcd34d', '#86efac', '#93c5fd', '#fda4af', '#fdba74',
    '#a5f3fc', '#bbf7d0', '#bfdbfe', '#fecaca', '#fed7aa',
    '#cffafe', '#dcfce7', '#dbeafe', '#fee2e2', '#ffedd5',
  ];

  const SOURCES = [
    { id: 'products',  label: 'Product Embeddings'  },
    { id: 'nutrition', label: 'Nutrition Embeddings'},
    { id: 'recipes',   label: 'Recipe Embeddings'   },
  ];

  function Badge({ children, variant }) {
    const tone = {
      green: 'bg-teal-500/20 text-teal-300',
      amber: 'bg-amber-500/20 text-amber-300',
      slate: 'bg-slate-500/20 text-slate-300',
      red:   'bg-red-500/20 text-red-300',
    }[variant] || 'bg-slate-500/20 text-slate-300';
    return <span className={`px-2 py-0.5 rounded text-xs font-mono ${tone}`}>{children}</span>;
  }

  function KpiCard({ label, value, sub }) {
    return (
      <div className="glass p-4">
        <div className="text-xs uppercase tracking-wider text-gray-400 mb-1" style={{ fontFamily: 'JetBrains Mono, monospace' }}>{label}</div>
        <div className="text-2xl font-semibold text-white">{value}</div>
        {sub && <div className="text-xs text-gray-500 mt-1">{sub}</div>}
      </div>
    );
  }

  function EmbeddingsProjector() {
    const [data, setData]       = useState(null);
    const [source, setSource]   = useState('products');
    const [search, setSearch]   = useState('');
    const [loading, setLoading] = useState(true);
    const [error, setError]     = useState(null);
    const [hovered, setHovered] = useState(null);
    const [selected, setSelected] = useState(null);
    const mountRef = useRef(null);
    const sceneStateRef = useRef(null);

    const load = useCallback(async () => {
      setLoading(true); setError(null);
      try {
        const url = window.EMBEDDING_SEED_URL || './data/embeddings-seed.json';
        const r = await fetch(url, { cache: 'no-cache' });
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const j = await r.json();
        // Filter on source + search client-side
        let pts = (j.points || []).slice();
        if (search) {
          const s = search.toLowerCase();
          pts = pts.filter(p => (p.name || '').toLowerCase().includes(s) || (p.brand || '').toLowerCase().includes(s) || (p.department || '').toLowerCase().includes(s));
        }
        setData({ ...j, points: pts, stats: { ...j.stats, count: pts.length, source } });
      } catch (e) {
        setError(e.message);
      } finally {
        setLoading(false);
      }
    }, [source, search]);

    useEffect(() => { load(); }, []);

    // Render scene whenever data changes
    useEffect(() => {
      if (!data || !mountRef.current) return;
      if (typeof THREE === 'undefined' || typeof THREE.OrbitControls === 'undefined') {
        setError('three.js / OrbitControls not loaded');
        return;
      }
      const OrbitControls = THREE.OrbitControls;
      let cancelled = false;
      let cleanup = null;
      (() => {
        if (cancelled || !mountRef.current) return;

        const mount = mountRef.current;
        // Clear any existing
        while (mount.firstChild) mount.removeChild(mount.firstChild);
        const w = mount.clientWidth || 720;
        const h = 520;

        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x0a0a14);
        const camera = new THREE.PerspectiveCamera(60, w / h, 0.1, 100);
        camera.position.set(2, 1.5, 2);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
        renderer.setSize(w, h);
        mount.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;

        const grid = new THREE.GridHelper(2, 10, 0x222244, 0x111133);
        grid.position.y = -1;
        scene.add(grid);
        const axes = new THREE.AxesHelper(1.2);
        axes.material.opacity = 0.30;
        axes.material.transparent = true;
        scene.add(axes);

        const pts = data.points;
        const N = pts.length;
        const positions = new Float32Array(N * 3);
        const colors = new Float32Array(N * 3);
        for (let i = 0; i < N; i++) {
          positions[i * 3]     = pts[i].x;
          positions[i * 3 + 1] = pts[i].y;
          positions[i * 3 + 2] = pts[i].z;
          const c = new THREE.Color(CATEGORY_PALETTE[(pts[i].color_idx ?? 0) % CATEGORY_PALETTE.length]);
          colors[i * 3]     = c.r;
          colors[i * 3 + 1] = c.g;
          colors[i * 3 + 2] = c.b;
        }
        const geom = new THREE.BufferGeometry();
        geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geom.setAttribute('color',    new THREE.BufferAttribute(colors, 3));
        const mat = new THREE.PointsMaterial({
          size: 0.04,
          vertexColors: true,
          opacity: 0.9,
          sizeAttenuation: true,
          transparent: true,
        });
        const cloud = new THREE.Points(geom, mat);
        scene.add(cloud);

        const raycaster = new THREE.Raycaster();
        raycaster.params.Points = { threshold: 0.04 };
        const pointer = new THREE.Vector2();

        function onPointer(ev) {
          const rect = renderer.domElement.getBoundingClientRect();
          pointer.x = ((ev.clientX - rect.left) / rect.width)  * 2 - 1;
          pointer.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
          raycaster.setFromCamera(pointer, camera);
          const hits = raycaster.intersectObject(cloud);
          if (hits.length > 0) {
            renderer.domElement.style.cursor = 'pointer';
            const idx = hits[0].index;
            setHovered(pts[idx]);
          } else {
            renderer.domElement.style.cursor = 'grab';
            setHovered(null);
          }
        }
        function onClick() {
          if (hovered) setSelected(hovered);
        }
        renderer.domElement.addEventListener('pointermove', onPointer);
        renderer.domElement.addEventListener('click', onClick);

        let frameId;
        const animate = () => {
          frameId = requestAnimationFrame(animate);
          controls.update();
          renderer.render(scene, camera);
        };
        animate();

        function onResize() {
          const nw = mount.clientWidth || 720;
          const nh = h;
          camera.aspect = nw / nh;
          camera.updateProjectionMatrix();
          renderer.setSize(nw, nh);
        }
        window.addEventListener('resize', onResize);

        cleanup = () => {
          cancelAnimationFrame(frameId);
          window.removeEventListener('resize', onResize);
          renderer.domElement.removeEventListener('pointermove', onPointer);
          renderer.domElement.removeEventListener('click', onClick);
          controls.dispose();
          geom.dispose();
          mat.dispose();
          renderer.dispose();
          if (mount.contains(renderer.domElement)) mount.removeChild(renderer.domElement);
        };
        sceneStateRef.current = { cleanup };
      })();

      return () => {
        cancelled = true;
        if (cleanup) cleanup();
      };
    }, [data]);

    // Build category legend
    const categories = useMemo(() => {
      if (!data) return [];
      const seen = new Map();
      data.points.forEach(p => {
        if (!seen.has(p.department)) seen.set(p.department, p.color_idx ?? 0);
      });
      return Array.from(seen.entries()).map(([name, color_idx]) => ({ name, color: CATEGORY_PALETTE[color_idx % CATEGORY_PALETTE.length] }));
    }, [data]);

    const detail = selected || hovered;

    return (
      <div className="space-y-6">
        <div className="glass p-6">
          <div className="flex items-baseline justify-between flex-wrap gap-2">
            <div>
              <h3 className="text-lg font-semibold text-white mb-1">Embeddings Projector</h3>
              <p className="text-gray-400 text-sm">
                768-dim product / nutrition / recipe vectors projected to 3D via PCA, rendered as a hoverable point cloud.
                Drag to orbit · scroll to zoom · click a point to pin.
              </p>
            </div>
            <span className="ai-badge teal">3D · WebGL</span>
          </div>

          {/* Controls */}
          <div className="flex flex-wrap gap-3 mt-5 mb-5">
            <select
              value={source}
              onChange={e => setSource(e.target.value)}
              className="bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500"
            >
              {SOURCES.map(s => <option key={s.id} value={s.id} className="bg-slate-800">{s.label}</option>)}
            </select>
            <input
              type="text"
              placeholder="Filter by name / brand / department…"
              value={search}
              onChange={e => setSearch(e.target.value)}
              className="flex-1 min-w-[200px] bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-gray-500 text-sm focus:outline-none focus:ring-2 focus:ring-teal-500"
            />
            <button
              onClick={load}
              className="bg-teal-600 hover:bg-teal-700 text-white px-4 py-2 rounded-lg font-medium text-sm transition-colors"
            >
              {loading ? <span className="inline-block w-4 h-4 border-2 border-white/40 border-t-white rounded-full animate-spin align-middle" /> : 'Project'}
            </button>
          </div>

          {/* KPI strip */}
          {data && (
            <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-5">
              <KpiCard label="Points" value={data.stats?.count?.toLocaleString() ?? '—'} />
              <KpiCard label="Source dimensions" value={data.stats?.dimensions_original ?? 768} sub="reduced via PCA → 3D" />
              <KpiCard label="Categories" value={categories.length} />
              <KpiCard label="Elapsed" value={`${data.stats?.elapsed_ms ?? 0} ms`} sub="server-side projection" />
            </div>
          )}

          {error && <div className="bg-red-500/10 border border-red-500/30 text-red-300 text-sm p-3 rounded-lg mb-3">{error}</div>}

          {/* 3D viewport + side panel */}
          <div className="grid grid-cols-1 lg:grid-cols-4 gap-4">
            <div className="lg:col-span-3">
              <div ref={mountRef} className="rounded-xl overflow-hidden border border-white/10" style={{ height: 520, background: '#0a0a14' }} />
            </div>
            <div className="lg:col-span-1">
              <div className="glass p-4 mb-3">
                <div className="text-xs uppercase tracking-wider text-gray-400 mb-2" style={{ fontFamily: 'JetBrains Mono, monospace' }}>Hover · click</div>
                {detail ? (
                  <div>
                    <div className="text-white font-semibold text-sm mb-1">{detail.name}</div>
                    <div className="text-xs text-gray-400 mb-2">{detail.department} · {detail.brand}</div>
                    <div className="flex gap-1 mb-3">
                      {detail.nutriscore && <Badge variant="green">Nutri {detail.nutriscore}</Badge>}
                      {detail.nova && <Badge variant="amber">NOVA {detail.nova}</Badge>}
                    </div>
                    <div className="text-[10px] text-gray-500 font-mono">
                      x: {detail.x?.toFixed(3)}<br />
                      y: {detail.y?.toFixed(3)}<br />
                      z: {detail.z?.toFixed(3)}
                    </div>
                    {selected && (
                      <button onClick={() => setSelected(null)} className="mt-3 text-xs text-teal-400 hover:text-teal-300">
                        Clear selection
                      </button>
                    )}
                  </div>
                ) : (
                  <div className="text-xs text-gray-500 italic">Hover a point to see SKU detail · click to pin.</div>
                )}
              </div>

              <div className="glass p-4">
                <div className="text-xs uppercase tracking-wider text-gray-400 mb-2" style={{ fontFamily: 'JetBrains Mono, monospace' }}>Color · category</div>
                <div className="space-y-1.5">
                  {categories.map(c => (
                    <div key={c.name} className="flex items-center gap-2 text-xs text-gray-300">
                      <span className="inline-block w-3 h-3 rounded-full" style={{ background: c.color }} />
                      <span className="truncate">{c.name}</span>
                    </div>
                  ))}
                </div>
              </div>
            </div>
          </div>
        </div>

        <div className="glass p-6">
          <div className="text-xs uppercase tracking-wider text-gray-400 mb-2" style={{ fontFamily: 'JetBrains Mono, monospace' }}>How this works</div>
          <p className="text-sm text-gray-300 leading-relaxed">
            Each point is a product, recipe, or nutrition concept embedded into a 768-dim vector by Delectable's
            <strong className="text-white"> embeddings_processor</strong> (see <code className="text-teal-300">pim/src/pim/embeddings_processor.py</code>),
            then collapsed to 3D via PCA server-side (<code className="text-teal-300">grocery-agent/routes.py:7227</code>) and streamed to the browser as
            <strong className="text-white"> normalized [−1, 1] coordinates</strong>. Spatial proximity ≈ semantic similarity: products that cluster together share
            ingredients, flavor profiles, dietary constraints, or purchase patterns. The cart-builder agent uses these same vectors at runtime to find
            substitutes, recommend pairings, and bind catalog queries to in-stock SKUs.
          </p>
        </div>
      </div>
    );
  }

  window.EmbeddingsProjector = EmbeddingsProjector;
})();
