// 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)',
];

function _grNodeLevel(c) {
  if (c.essences?.length) return 'essence';
  if (c.patterns?.length) return 'pattern';
  if (c.perspectives?.length) return 'perspective';
  if (c.function) return 'function';
  if (c.consist) return 'consist';
  return null;
}

function _grRoundedPolygon(cx, cy, r, sides, rot) {
  const a0 = rot !== undefined ? rot : -Math.PI / 2;
  const pts = Array.from({ length: sides }, (_, i) => {
    const a = a0 + (i / sides) * Math.PI * 2;
    return { x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r };
  });
  const t = 0.12;
  const d = [];
  for (let i = 0; i < sides; i++) {
    const p = pts[i], prev = pts[(i - 1 + sides) % sides], next = pts[(i + 1) % sides];
    const sx = prev.x + t * (p.x - prev.x), sy = prev.y + t * (p.y - prev.y);
    const ex = p.x + t * (next.x - p.x), ey = p.y + t * (next.y - p.y);
    d.push(i === 0 ? `M${sx},${sy}` : `L${sx},${sy}`);
    d.push(`Q${p.x},${p.y} ${ex},${ey}`);
  }
  return d.join(' ') + ' Z';
}

function _grLevelPath(cx, cy, r, level) {
  switch (level) {
    case 'consist':     return _grRoundedPolygon(cx, cy, r, 3, Math.PI / 2);
    case 'function':    return _grRoundedPolygon(cx, cy, r, 4, -Math.PI / 2);
    case 'perspective': return _grRoundedPolygon(cx, cy, r, 5, -Math.PI / 2);
    case 'pattern':     return _grRoundedPolygon(cx, cy, r, 6, -Math.PI / 2);
    default:            return null;
  }
}


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(--fg)' : "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, onOpenConcept, onOpenGraph, onOpenSolar, onOpenCardsHub, onUpdate, density, initialSelected, onBack }) {
  const _grIsNature = !!document.querySelector('.app-shell[data-nature]');
  const nodesRef = _cl_ur(null);
  if (!nodesRef.current || nodesRef.current.length !== concepts.length) {
    nodesRef.current = _grInit(concepts);
  }

  const _cgs = (() => { try { return JSON.parse(localStorage.getItem('disene_group_settings')) || {}; } catch { return {}; } })();

  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(780);
  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(_cgs.groupMode ?? 'links');

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

  _cl_ue(() => {
    localStorage.setItem('disene_group_settings', JSON.stringify({ groupMode, showEdges, nodeScale, nodeSpread, groupSep }));
  }, [groupMode, showEdges, nodeScale, nodeSpread, groupSep]);

  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;

  return (
    <div style={{ display: "flex", height: "100vh", overflow: "hidden", background: "var(--app-bg, var(--bg))", backgroundImage: "radial-gradient(circle, var(--dot-color) 1px, transparent 1px)", backgroundSize: "28px 28px", paddingRight: 8 }}>

      {/* Canvas */}
      <div
        ref={containerRef}
        style={{ flex: 1, position: "relative", overflow: "hidden" }}
        onPointerMove={onMove}
        onPointerUp={onUp}
      >
        <svg ref={svgRef} style={{ width: "100%", height: "100%", display: "block" }} onPointerDown={onSvgDown}>
          <defs>
            <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})`}>

            {/* 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={_grIsNature ? 'rgba(255,255,255,0.5)' : (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 - 28}
                  textAnchor="middle"
                  fontFamily="var(--font-mono)" fontSize="13" fontWeight="600" letterSpacing="1.8"
                  fill={_grIsNature ? 'rgba(255,255,255,0.6)' : 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 = _grIsNature ? 'rgba(255,255,255,0.25)' : (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); }}
                >
                  {(() => {
                    if (_grIsNature) {
                      const level = _grNodeLevel(c);
                      const d = level ? _grLevelPath(p.x, p.y, r, level) : null;
                      const fillCol = isHov || isSel ? 'rgba(255,255,255,0.22)' : 'rgba(255,255,255,0.10)';
                      const strokeCol = isHov || isSel ? 'rgba(255,255,255,0.95)' : 'rgba(255,255,255,0.55)';
                      if (d) return <path d={d} fill={fillCol} stroke={strokeCol} strokeWidth={isSel ? 2 : 1.5} />;
                      return <circle cx={p.x} cy={p.y} r={r} fill={fillCol} stroke={strokeCol} strokeWidth={isSel ? 2 : 1.5} />;
                    }
                    return <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: 106, right: 14, display: "flex", alignItems: "center", gap: 10, padding: "6px 10px" }}>
          {/* Mode switcher */}
          <div style={{ display: 'flex', gap: 2 }}>
            {[
              { id: 'links', title: 'Links', icon: window.Icons.Link(18) },
              { id: 'steps', title: 'Steps', icon: <svg width={18} height={18} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><line x1="9" y1="6" x2="20" y2="6" /><line x1="9" y1="12" x2="20" y2="12" /><line x1="9" y1="18" x2="20" y2="18" /><circle cx="4" cy="6" r="1.5" /><circle cx="4" cy="12" r="1.5" /><circle cx="4" cy="18" r="1.5" /></svg> },
              { id: 'categories', title: 'Categories', icon: window.Icons.Grid(18) },
            ].map(({ id, title, icon }) => {
              const active = groupMode === id;
              return (
                <button key={id} className="btn tiny" title={title}
                  style={{
                    border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center',
                    background: active ? 'var(--bg-sunk)' : 'transparent',
                    boxShadow: 'none',
                    color: active ? 'var(--fg)' : 'var(--fg-3)',
                  }}
                  onClick={() => setGroupMode(id)}>{icon}</button>
              );
            })}
          </div>

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

          <div className="view-mode-sw mode-pill">
            <button className="btn tiny" style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: "transparent", color: "var(--fg-3)", opacity: 1 }} onClick={() => selected ? onOpenConcept(selected, 'edit') : onBack && onBack()}>{window.Icons.Word(24)}</button>
            <button className="btn tiny" style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: "transparent", color: "var(--fg-3)" }} onClick={() => onOpenCardsHub && onOpenCardsHub(selected || null)}>{window.Icons.Cards(24)}</button>
            <button className="btn tiny" style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: "transparent", color: "var(--fg-3)" }} onClick={() => selected ? onOpenSolar(selected, true) : onOpenSolar && onOpenSolar(null)}>{window.Icons.Planet(24)}</button>
            <button className="btn tiny" style={{ border: 0, width: 36, height: 36, padding: 0, justifyContent: 'center', background: "transparent", color: "var(--fg-3)" }} onClick={() => onOpenGraph && onOpenGraph(selected)}>{window.Icons.Graph(24)}</button>
          </div>
        </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(24)}
        </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} />
            <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)",
            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-ui)", color: "var(--fg-2)", fontSize: 14, marginTop: 6, lineHeight: 1.5 }}>
                {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 side-edit-panel" style={{
          position: "relative",
          width: panelW, flexShrink: 0,
          height: "calc(100% - 36px)",
          margin: "18px 8px 18px 0",
          borderRadius: 'var(--ce-panel-radius, 14px)',
          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;
