// group.jsx — Force-directed grouping view.
// Supports three grouping modes: by link connectivity, concept state steps, or category.

const { useState: _cl_us, useEffect: _cl_ue, useRef: _cl_ur, useMemo: _cl_um } = React;

// ─── Grouping helpers ─────────────────────────────────────────────

function _grInit(concepts) {
  const angle = (2 * Math.PI) / Math.max(concepts.length, 1);
  const r = Math.max(80, 45 * Math.sqrt(concepts.length));
  return concepts.map((c, i) => ({
    id: c.id,
    x: Math.cos(i * angle) * r + (Math.random() - 0.5) * 30,
    y: Math.sin(i * angle) * r + (Math.random() - 0.5) * 30,
    vx: 0, vy: 0, pinned: false,
  }));
}

function _grDetect(concepts) {
  const parent = {};
  concepts.forEach(c => { parent[c.id] = c.id; });
  function find(x) {
    while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; }
    return x;
  }
  concepts.forEach(c => {
    (c.links || []).forEach(l => {
      const tid = typeof l === 'string' ? l : l.targetId;
      if (concepts.find(x => x.id === tid)) parent[find(c.id)] = find(tid);
    });
  });
  const out = {};
  concepts.forEach(c => {
    const root = find(c.id);
    if (!out[root]) out[root] = [];
    out[root].push(c.id);
  });
  return out;
}

function _grStepKey(c) {
  if ((c.essences || []).length > 0)     return 'essences';
  if ((c.patterns || []).length > 0)     return 'patterns';
  if ((c.perspectives || []).length > 0) return 'perspectives';
  if (c.function)                         return 'function';
  if (c.consist)                          return 'consist';
  return 'empty';
}

function _grGroupBy(concepts, mode) {
  if (mode === 'links') return _grDetect(concepts);
  const out = {};
  concepts.forEach(c => {
    const g = mode === 'steps' ? _grStepKey(c) : (c.category || 'uncategorized');
    if (!out[g]) out[g] = [];
    out[g].push(c.id);
  });
  return out;
}

// ─── Constants ────────────────────────────────────────────────────

const _CL_STEP_ORDER = ['empty', 'consist', 'function', 'perspectives', 'patterns', 'essences'];

const _CL_STEP_COLORS = {
  empty:        'var(--fg-3)',
  consist:      'var(--c-consist)',
  'function':   'var(--c-function)',
  perspectives: 'var(--c-perspective)',
  patterns:     'var(--c-pattern)',
  essences:     'var(--c-essence)',
};

const _CL_COLORS = [
  'var(--c-perspective)', 'var(--c-pattern)', 'var(--c-consist)',
  'var(--c-function)', 'var(--c-essence)',
];

const _CL_MODES = [
  { id: 'links',      label: 'Links' },
  { id: 'steps',      label: 'Steps' },
  { id: 'categories', label: 'Categories' },
];

const _CL_BASE_REPULSE = 14000;
const _CL_SPRING_K     = 0.022;
const _CL_SPRING_L     = 130;
const _CL_DAMP         = 0.84;
const _CL_GRAV         = 0.005;
const _CL_PULL         = 0.009;

// ─── Settings UI helpers ──────────────────────────────────────────

function _ClSlider({ label, value, min, max, step, onChange }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      <div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, color: "var(--fg-3)", fontFamily: "var(--font-mono)", letterSpacing: ".04em" }}>
        <span>{label}</span>
        <span>{value}</span>
      </div>
      <input type="range" min={min} max={max} step={step} value={value}
        onChange={e => onChange(Number(e.target.value))}
        style={{ width: "100%", cursor: "pointer" }}
      />
    </div>
  );
}

function _ClToggle({ label, value, onChange }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
      <span style={{ fontSize: 11, color: "var(--fg-3)", fontFamily: "var(--font-mono)", letterSpacing: ".04em" }}>{label}</span>
      <button
        type="button" onClick={() => onChange(!value)} role="switch" aria-checked={!!value}
        style={{
          position: "relative", width: 36, height: 20, border: "none", borderRadius: 999,
          background: value ? "var(--c-nucleus)" : "var(--border)",
          cursor: "pointer", transition: "background 200ms", flexShrink: 0, padding: 0,
        }}
      >
        <span style={{
          position: "absolute", top: 2, left: value ? 18 : 2,
          width: 16, height: 16, borderRadius: "50%", background: "#fff",
          boxShadow: "0 1px 3px rgba(0,0,0,.25)", transition: "left 200ms", display: "block",
        }} />
      </button>
    </div>
  );
}

// ─── Component ────────────────────────────────────────────────────

function GroupScreen({ concepts, onBack, onOpenConcept, onOpenGraph, onOpenSolar, onOpenCardsHub, onUpdate, density, initialSelected }) {
  const nodesRef = _cl_ur(null);
  if (!nodesRef.current || nodesRef.current.length !== concepts.length) {
    nodesRef.current = _grInit(concepts);
  }

  const [, rerender]     = _cl_us(0);
  const [hover, setHover]     = _cl_us(null);
  const [selected, setSelected] = _cl_us(initialSelected || null);
  const [cardPanel, setCardPanel] = _cl_us(null);
  _cl_ue(() => { setCardPanel(null); }, [selected]);
  const [panelW, setPanelW] = _cl_us(460);
  const resizingRef = _cl_ur(null);
  function onResizeStart(e) {
    e.preventDefault();
    resizingRef.current = { startX: e.clientX, startW: panelW };
    const onMove = (ev) => {
      const delta = resizingRef.current.startX - ev.clientX;
      setPanelW(Math.max(280, Math.min(820, resizingRef.current.startW + delta)));
    };
    const onUp = () => {
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
      resizingRef.current = null;
    };
    document.addEventListener('pointermove', onMove);
    document.addEventListener('pointerup', onUp);
  }
  const [tipPos, setTipPos]   = _cl_us({ x: 0, y: 0 });
  const [pan, setPan]     = _cl_us({ x: 0, y: 0 });
  const [zoom, setZoom]   = _cl_us(0.9);
  const [size, setSize]   = _cl_us({ w: 800, h: 600 });
  const [groupMode, setGroupMode] = _cl_us('links');

  // Settings
  const [showSettings, setShowSettings] = _cl_us(false);
  const [showEdges,    setShowEdges]    = _cl_us(false);
  const [nodeScale,    setNodeScale]    = _cl_us(1);
  const [blobScale,    setBlobScale]    = _cl_us(1);
  const [nodeSpread,   setNodeSpread]   = _cl_us(5);
  const [groupSep,   setGroupSep]   = _cl_us(5);

  const containerRef  = _cl_ur(null);
  const svgRef        = _cl_ur(null);
  const panRef        = _cl_ur({ x: 0, y: 0 });
  const zoomRef       = _cl_ur(0.9);
  const dragNode      = _cl_ur(null);
  const dragOffset    = _cl_ur({ x: 0, y: 0 });
  const dragBg        = _cl_ur(null);
  const moved         = _cl_ur(false);
  const modeRef       = _cl_ur(groupMode);
  const nodeSpreadRef = _cl_ur(nodeSpread);
  const groupSepRef = _cl_ur(groupSep);
  modeRef.current       = groupMode;
  nodeSpreadRef.current = nodeSpread;
  groupSepRef.current = groupSep;

  // Stable fingerprint across all modes
  const fingerprint = concepts.map(c =>
    c.id + '|' + (c.category || '') + '|' + _grStepKey(c) + '|' + JSON.stringify(c.links)
  ).join('\n');

  const groups = _cl_um(() => _grGroupBy(concepts, groupMode),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [groupMode, fingerprint]);

  const nodeGroup = _cl_um(() => {
    const map = {};
    Object.entries(groups).forEach(([root, ids]) => ids.forEach(id => { map[id] = root; }));
    return map;
  }, [groups]);

  const groupList = _cl_um(() => {
    const entries = Object.entries(groups);
    if (groupMode === 'steps') {
      return entries
        .sort(([a], [b]) => _CL_STEP_ORDER.indexOf(a) - _CL_STEP_ORDER.indexOf(b))
        .map(([root, ids]) => ({ root, ids, color: _CL_STEP_COLORS[root] || 'var(--fg-3)' }));
    }
    return entries.map(([root, ids], i) => ({
      root, ids, color: _CL_COLORS[i % _CL_COLORS.length],
    }));
  }, [groups, groupMode]);

  const colorOf = _cl_um(() => {
    const map = {};
    groupList.forEach(({ root, color }) => { map[root] = color; });
    return map;
  }, [groupList]);

  const edges = _cl_um(() => {
    const list = [], seen = new Set();
    concepts.forEach(c => {
      (c.links || []).forEach(l => {
        const tid  = typeof l === 'string' ? l : l.targetId;
        const type = typeof l === 'string' ? 'unknown' : (l.type || 'unknown');
        const key  = [c.id, tid].sort().join('\0');
        if (!seen.has(key) && concepts.find(x => x.id === tid)) {
          seen.add(key); list.push([c.id, tid, type]);
        }
      });
    });
    return list;
  }, [concepts]);

  // Resize
  _cl_ue(() => {
    const el = containerRef.current;
    if (!el) return;
    const ro = new ResizeObserver(([e]) => {
      const { width, height } = e.contentRect;
      setSize({ w: width, h: height });
    });
    ro.observe(el);
    setSize({ w: el.clientWidth, h: el.clientHeight });
    return () => ro.disconnect();
  }, []);

  // Wheel zoom
  _cl_ue(() => {
    const el = svgRef.current;
    if (!el) return;
    const h = e => {
      e.preventDefault();
      zoomRef.current = Math.max(0.15, Math.min(6, zoomRef.current * Math.exp(-e.deltaY * 0.001)));
      setZoom(zoomRef.current);
    };
    el.addEventListener('wheel', h, { passive: false });
    return () => el.removeEventListener('wheel', h);
  }, []);

  // Force simulation
  _cl_ue(() => {
    let alive = true, raf;
    const ns = nodesRef.current;
    const nm = {};
    ns.forEach(n => { nm[n.id] = n; });

    function step() {
      if (!alive) return;

      const cen = {}, cnt = {};
      ns.forEach(n => {
        const cl = nodeGroup[n.id] || n.id;
        if (!cen[cl]) { cen[cl] = { x: 0, y: 0 }; cnt[cl] = 0; }
        cen[cl].x += n.x; cen[cl].y += n.y; cnt[cl]++;
      });
      Object.keys(cen).forEach(cl => { cen[cl].x /= cnt[cl]; cen[cl].y /= cnt[cl]; });

      // Repulsion — tuned by slider refs (no restart needed)
      const sameF = _CL_BASE_REPULSE * 0.09 * nodeSpreadRef.current;
      const diffF = _CL_BASE_REPULSE * 0.40 * groupSepRef.current;
      for (let i = 0; i < ns.length; i++) {
        for (let j = i + 1; j < ns.length; j++) {
          const a = ns[i], b = ns[j];
          const dx = b.x - a.x, dy = b.y - a.y;
          const d = Math.sqrt(dx * dx + dy * dy) || 1;
          const same = (nodeGroup[a.id] || a.id) === (nodeGroup[b.id] || b.id);
          const f = (same ? sameF : diffF) / (d * d);
          const fx = f * dx / d, fy = f * dy / d;
          if (!a.pinned) { a.vx -= fx; a.vy -= fy; }
          if (!b.pinned) { b.vx += fx; b.vy += fy; }
        }
      }

      // Spring forces — only in links mode
      if (modeRef.current === 'links') {
        edges.forEach(([aId, bId]) => {
          const a = nm[aId], b = nm[bId];
          if (!a || !b) return;
          const dx = b.x - a.x, dy = b.y - a.y;
          const d = Math.sqrt(dx * dx + dy * dy) || 1;
          const f = (d - _CL_SPRING_L) * _CL_SPRING_K;
          const fx = f * dx / d, fy = f * dy / d;
          if (!a.pinned) { a.vx += fx; a.vy += fy; }
          if (!b.pinned) { b.vx -= fx; b.vy -= fy; }
        });
      }

      ns.forEach(n => {
        if (n.pinned) return;
        const cl = nodeGroup[n.id] || n.id;
        const c = cen[cl];
        if (!c) return;
        n.vx += (c.x - n.x) * _CL_PULL;
        n.vy += (c.y - n.y) * _CL_PULL;
      });

      ns.forEach(n => {
        if (n.pinned) return;
        n.vx += -n.x * _CL_GRAV; n.vy += -n.y * _CL_GRAV;
        n.vx *= _CL_DAMP;        n.vy *= _CL_DAMP;
        n.x  += n.vx;            n.y  += n.vy;
      });

      rerender(r => r + 1);
      raf = requestAnimationFrame(step);
    }
    raf = requestAnimationFrame(step);
    return () => { alive = false; cancelAnimationFrame(raf); };
  }, [edges, nodeGroup]);

  // ─── Interaction ──────────────────────────────────────────────

  function toSim(clientX, clientY) {
    const svg = svgRef.current;
    if (!svg) return { x: 0, y: 0 };
    const rect = svg.getBoundingClientRect();
    return {
      x: (clientX - rect.left  - size.w / 2 - panRef.current.x) / zoomRef.current,
      y: (clientY - rect.top   - size.h / 2 - panRef.current.y) / zoomRef.current,
    };
  }

  function onSvgDown(e) {
    moved.current = false;
    dragBg.current = { sx: e.clientX, sy: e.clientY, sp: { ...panRef.current } };
  }

  function onNodeDown(e, id) {
    e.stopPropagation();
    moved.current = false;
    const n = nodesRef.current.find(n => n.id === id);
    if (n) {
      n.pinned = true;
      const cur = toSim(e.clientX, e.clientY);
      dragOffset.current = { x: cur.x - n.x, y: cur.y - n.y };
    }
    dragNode.current = id;
    dragBg.current = null;
    e.currentTarget.setPointerCapture?.(e.pointerId);
  }

  function onMove(e) {
    moved.current = true;
    setTipPos({ x: e.clientX, y: e.clientY });
    if (dragNode.current) {
      const { x, y } = toSim(e.clientX, e.clientY);
      const n = nodesRef.current.find(n => n.id === dragNode.current);
      if (n) { n.x = x - dragOffset.current.x; n.y = y - dragOffset.current.y; n.vx = 0; n.vy = 0; }
    } else if (dragBg.current) {
      const p = { x: dragBg.current.sp.x + e.clientX - dragBg.current.sx, y: dragBg.current.sp.y + e.clientY - dragBg.current.sy };
      panRef.current = p; setPan(p);
    }
  }

  function onUp() {
    if (dragNode.current) {
      const n = nodesRef.current.find(n => n.id === dragNode.current);
      if (n) { n.pinned = false; n.vx = 0; n.vy = 0; }
      dragNode.current = null;
    } else if (dragBg.current && !moved.current) {
      setSelected(null);
    }
    dragBg.current = null;
  }

  // ─── Render ───────────────────────────────────────────────────

  const posMap = {};
  nodesRef.current?.forEach(n => { posMap[n.id] = { x: n.x, y: n.y }; });

  const ox = size.w / 2 + pan.x;
  const oy = size.h / 2 + pan.y;
  const hoverConcept    = hover    ? concepts.find(c => c.id === hover)    : null;
  const selectedConcept = selected ? concepts.find(c => c.id === selected) : null;
  const blobR = 52 * blobScale;

  return (
    <div style={{ display: "flex", height: "100vh", overflow: "hidden", background: "var(--bg)" }}>

      {/* Canvas */}
      <div
        ref={containerRef}
        style={{ flex: 1, position: "relative", overflow: "hidden", background: "var(--bg)" }}
        onPointerMove={onMove}
        onPointerUp={onUp}
      >
        <svg ref={svgRef} style={{ width: "100%", height: "100%", display: "block" }} onPointerDown={onSvgDown}>
          <defs>
            <filter id="cl-glow" x="-80%" y="-80%" width="260%" height="260%">
              <feGaussianBlur in="SourceGraphic" stdDeviation="28" />
            </filter>
            <filter id="node-blur" x="-80%" y="-80%" width="260%" height="260%">
              <feGaussianBlur in="SourceGraphic" stdDeviation="6" />
            </filter>
          </defs>

          <g transform={`translate(${ox},${oy}) scale(${zoom})`}>

            {/* Group glow blobs */}
            {groupList.map(({ root, ids, color }) => (
              <g key={`glow-${root}`} filter="url(#cl-glow)" opacity="0.22">
                {ids.map(id => {
                  const p = posMap[id];
                  return p ? <circle key={id} cx={p.x} cy={p.y} r={blobR} fill={color} /> : null;
                })}
              </g>
            ))}

            {/* Edges */}
            {showEdges && edges.map(([aId, bId]) => {
              const a = posMap[aId], b = posMap[bId];
              if (!a || !b) return null;
              const sameGroup = (nodeGroup[aId] || aId) === (nodeGroup[bId] || bId);
              return (
                <line key={`${aId}\0${bId}`}
                  x1={a.x} y1={a.y} x2={b.x} y2={b.y}
                  stroke={sameGroup ? colorOf[nodeGroup[aId] || aId] || 'var(--fg-3)' : 'var(--fg-3)'}
                  strokeOpacity={sameGroup ? 0.35 : 0.18}
                  strokeWidth="1"
                />
              );
            })}

            {/* Group labels */}
            {groupList.map(({ root, ids, color }) => {
              if (groupMode === 'links' && ids.length < 2) return null;
              const pts = ids.map(id => posMap[id]).filter(Boolean);
              if (!pts.length) return null;
              const cx = pts.reduce((s, p) => s + p.x, 0) / pts.length;
              const minY = Math.min(...pts.map(p => p.y));
              const label = groupMode === 'links' ? 'group' : root;
              return (
                <text key={`lbl-${root}`} x={cx} y={minY - blobR * 0.55}
                  textAnchor="middle"
                  fontFamily="var(--font-mono)" fontSize="13" fontWeight="600" letterSpacing="1.8"
                  fill={color} opacity="0.7" style={{ textTransform: "uppercase", userSelect: "none" }}>
                  {label}
                </text>
              );
            })}

            {/* Nodes */}
            {concepts.map(c => {
              const p = posMap[c.id];
              if (!p) return null;
              const isHov = hover    === c.id;
              const isSel = selected === c.id;
              const col   = colorOf[nodeGroup[c.id] || c.id] || 'var(--fg-3)';
              const r     = (10 + window.fillCount(c) * 2.2) * nodeScale;
              const dim   = hover && !isHov && !isSel ? 0.2 : 1;
              return (
                <g key={c.id} opacity={dim} style={{ cursor: 'pointer' }}
                  onPointerDown={e => onNodeDown(e, c.id)}
                  onPointerEnter={() => setHover(c.id)}
                  onPointerLeave={() => setHover(null)}
                  onClick={() => { if (!moved.current) setSelected(s => s === c.id ? null : c.id); }}
                >
                  <circle cx={p.x} cy={p.y} r={r}
                    fill={col} filter={isSel ? undefined : "url(#node-blur)"}
                    opacity={isHov || isSel ? 1 : 0.7}
                  />
                  <text x={p.x} y={p.y + r + 13}
                    textAnchor="middle"
                    fontFamily="var(--font-ui)" fontSize="11"
                    fill={isHov || isSel ? "var(--fg)" : "var(--fg-2)"}
                    fontWeight={isHov || isSel ? "500" : "400"}
                    style={{ userSelect: "none" }}
                  >{c.title}</text>
                </g>
              );
            })}
          </g>
        </svg>

        {/* Toolbar */}
        <div style={{ position: "absolute", top: 14, left: 98, right: 14, display: "flex", alignItems: "center", gap: 10, padding: "6px 10px" }}>
          <button className="btn ghost tiny" onClick={onBack}>{window.Icons.ArrowL(13)} Library</button>

          {/* Mode switcher */}
          <div className="mode-pill">
            {_CL_MODES.map(({ id, label }) => (
              <button key={id} className="btn tiny"
                style={{
                  border: 0,
                  background: groupMode === id ? "var(--c-nucleus)" : "transparent",
                  boxShadow: groupMode === id ? "0 1px 3px rgba(0,0,0,.15)" : "none",
                  color: groupMode === id ? "rgba(0,0,0,0.8)" : "var(--fg-3)",
                }}
                onClick={() => setGroupMode(id)}>{label}</button>
            ))}
          </div>

          <div style={{ flex: 1 }} />

          <div className="view-mode-sw mode-pill">
            <button className="btn tiny" style={{ border: 0, background: "transparent", color: "var(--fg-3)", opacity: selected ? 1 : 0.35 }} onClick={() => selected && onOpenConcept(selected, 'edit')}>{window.Icons.Word(13)} Word</button>
            <button className="btn tiny" style={{ border: 0, background: "transparent", color: "var(--fg-3)" }} onClick={() => onOpenCardsHub && onOpenCardsHub(selected || null)}>{window.Icons.Cards(13)} Card</button>
            <button className="btn tiny" style={{ border: 0, background: "transparent", color: "var(--fg-3)", opacity: selected ? 1 : 0.35 }} onClick={() => selected && onOpenSolar(selected, true)}>{window.Icons.Planet(13)} Solar</button>
            <button className="btn tiny" style={{ border: 0, background: "transparent", color: "var(--fg-3)" }} onClick={() => onOpenGraph && onOpenGraph(selected)}>{window.Icons.Graph(13)} Graph</button>
            <button className="btn tiny" data-active="true" style={{ border: 0, background: "var(--c-nucleus)", boxShadow: "0 1px 3px rgba(0,0,0,.15)", color: "rgba(0,0,0,0.8)" }}>{window.Icons.Atom(13)} Group</button>
          </div>
        </div>

        {/* Hint */}
        <div style={{ position: "absolute", bottom: 16, left: "50%", transform: "translateX(-50%)", fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--fg-3)", letterSpacing: ".08em", textTransform: "uppercase", pointerEvents: "none" }}>
          drag · scroll to zoom
        </div>

        {/* Settings button */}
        <button
          className="btn icon"
          style={{ position: "absolute", bottom: 16, right: 16, borderRadius: 8, padding: 7, width: 32, height: 32, justifyContent: "center", border: "none", background: showSettings ? "var(--bg-sunk)" : "var(--bg-elev)", boxShadow: showSettings ? "0 1px 2px rgba(0,0,0,.04)" : "0 1px 2px rgba(0,0,0,.06), 0 4px 16px -4px rgba(0,0,0,.1)" }}
          onClick={() => setShowSettings(s => !s)}
          title="Group settings"
        >
          {window.Icons.Sliders(16)}
        </button>

        {/* Settings panel */}
        {showSettings && (
          <div style={{
            position: "absolute", bottom: 58, right: 16, zIndex: 20,
            background: "var(--bg-elev)",
            borderRadius: 14, padding: "14px 16px",
            boxShadow: "0 4px 24px rgba(0,0,0,.12), 0 1px 4px rgba(0,0,0,.06)",
            minWidth: 230, display: "flex", flexDirection: "column", gap: 12,
          }}>
            <div style={{ fontSize: 10, fontFamily: "var(--font-mono)", letterSpacing: ".08em", textTransform: "uppercase", color: "var(--fg-3)", paddingBottom: 2 }}>Display</div>
            <_ClToggle label="Show edges" value={showEdges} onChange={setShowEdges} />
            <div style={{ height: 1, background: "var(--border-soft)" }} />
            <_ClSlider label="Node size"          value={nodeScale}  min={0.4} max={2.5} step={0.1} onChange={setNodeScale} />
            <_ClSlider label="Blob size"          value={blobScale}  min={0.3} max={3}   step={0.1} onChange={setBlobScale} />
            <div style={{ height: 1, background: "var(--border-soft)" }} />
            <_ClSlider label="Node spacing"       value={nodeSpread} min={1}   max={10}  step={1}   onChange={setNodeSpread} />
            <_ClSlider label="Group separation" value={groupSep} min={1}   max={10}  step={1}   onChange={setGroupSep} />
          </div>
        )}

        {/* Hover card */}
        {hoverConcept && (
          <div style={{
            position: "fixed", left: tipPos.x + 16, top: tipPos.y - 10,
            background: "var(--bg-elev)", color: "var(--fg)", border: "1px solid var(--border)",
            borderRadius: 10, padding: "12px 16px", pointerEvents: "none",
            boxShadow: "var(--shadow-lift)", minWidth: 200, maxWidth: 300, zIndex: 200,
          }}>
            <div className="tag-flat" style={{ marginBottom: 4 }}>{hoverConcept.category}</div>
            <div className="concept-title" style={{ fontSize: 18 }}>{hoverConcept.title}</div>
            {hoverConcept.essences?.[0]?.body && (
              <div style={{ fontFamily: "var(--font-serif)", color: "var(--fg-2)", fontSize: 14, marginTop: 6, lineHeight: 1.5, fontStyle: "italic" }}>
                {hoverConcept.essences[0].body}
              </div>
            )}
            <div style={{ marginTop: 8, fontSize: 10, color: "var(--fg-3)", fontFamily: "var(--font-mono)", letterSpacing: ".06em", textTransform: "uppercase" }}>
              {window.fillCount(hoverConcept)}/5 complete
            </div>
          </div>
        )}
      </div>

      {selectedConcept && (
        <div className="no-scrollbar" style={{
          position: "relative",
          width: panelW, flexShrink: 0,
          height: "calc(100% - 16px)",
          margin: "8px 8px 8px 0",
          borderRadius: 14,
          background: "var(--bg-elev)",
          boxShadow: "0 1px 2px rgba(0,0,0,.04), 0 4px 20px -6px rgba(0,0,0,.08)",
          overflowY: "auto",
        }}>
          <div onPointerDown={onResizeStart} style={{
            position: "absolute", left: 0, top: 0, bottom: 0, width: 6,
            cursor: "col-resize", zIndex: 10,
          }} />
          {cardPanel
            ? <window.CardsSidePanel
                conceptId={selectedConcept.id}
                parentType={cardPanel.parentType}
                parentId={cardPanel.parentId}
                label={cardPanel.label}
                onBack={() => setCardPanel(null)}
              />
            : <window.ConceptEdit
                concept={selectedConcept}
                allConcepts={concepts}
                onChange={(patch) => onUpdate && onUpdate(selectedConcept.id, patch)}
                onBack={() => setSelected(null)}
                onSwitchMode={(m) => onOpenConcept(selectedConcept.id, m)}
                onOpenSolar={() => onOpenSolar && onOpenSolar(selectedConcept.id)}
                onOpenConcept={setSelected}
                onOpenCards={(parentType, parentId, label) => setCardPanel({ parentType, parentId, label })}
                density={density || 'regular'}
                panelMode={true}
              />
          }
        </div>
      )}
    </div>
  );
}

window.GroupScreen = GroupScreen;
