// Group visuals — split into two components so Canvas can draw them at
// different z-order layers:
//
//   GroupBlob   — the filtered blob fill + spoke connectors (goes BELOW edges/nodes)
//   GroupLabel  — the colored name pill (goes ABOVE edges/nodes so it never gets hidden,
//                 and can be dragged freely to any offset via group.labelOffset)
//
// Non-group nodes that happen to sit between members are intentionally
// ignored — the connecting strokes pass right through them.

function GroupBlob({ group, nodes, selected, composing, onSelect }){
  const memberNodes = (group.nodeIds || [])
    .map(nid => nodes.find(n => n.id === nid))
    .filter(Boolean);
  if (memberNodes.length === 0) return null;

  const R = 55;              // member blob radius
  const LINK_W = 70;         // width of the connector line (must survive the alpha threshold)
  const fill = group.color || '#FFB3C7';

  // Group centroid — we spoke-connect every member to this point so that
  // spread-out groups still read as one shape after the metaball filter.
  const cx = memberNodes.reduce((s,n) => s + n.x, 0) / memberNodes.length;
  const cy = memberNodes.reduce((s,n) => s + n.y, 0) / memberNodes.length;

  return (
    <g className={`group-blob${selected ? ' selected' : ''}${composing ? ' composing' : ''}`}
       data-group-id={group.id}
       onClick={(e)=>{ e.stopPropagation(); onSelect && onSelect(group.id); }}
       style={{cursor:'pointer'}}>
      {/* Filtered blob: circles at each member + thick spoke lines toward centroid.
          The shared #group-goo filter merges everything into a single gooey shape. */}
      <g filter="url(#group-goo)" opacity="0.42">
        {memberNodes.length > 1 && memberNodes.map(n => (
          <line key={'link-'+n.id}
                x1={n.x} y1={n.y} x2={cx} y2={cy}
                stroke={fill}
                strokeWidth={LINK_W}
                strokeLinecap="round"/>
        ))}
        {memberNodes.map(n => (
          <circle key={n.id} cx={n.x} cy={n.y} r={R} fill={fill} />
        ))}
      </g>
    </g>
  );
}

function GroupLabel({ group, nodes, updateGroup, worldZoom }){
  if (group.showName === false || !group.name) return null;

  const memberNodes = (group.nodeIds || [])
    .map(nid => nodes.find(n => n.id === nid))
    .filter(Boolean);
  if (memberNodes.length === 0) return null;

  const R = 55;
  const fill = group.color || '#FFB3C7';

  // Base position: horizontally centered on bbox, slightly INSIDE the top of the blob
  // so the pill reads as "attached" to the group rather than floating above.
  let minX=Infinity, minY=Infinity, maxX=-Infinity;
  memberNodes.forEach(n => {
    minX = Math.min(minX, n.x - R);
    minY = Math.min(minY, n.y - R);
    maxX = Math.max(maxX, n.x + R);
  });
  const basePos = { x: (minX + maxX) / 2, y: minY + 12 };

  // User-customised offset (optional). Default to no offset for backward compat.
  const offset = group.labelOffset || { dx: 0, dy: 0 };
  const pos = { x: basePos.x + (offset.dx || 0), y: basePos.y + (offset.dy || 0) };

  // Pick a readable text color for the name pill (fill is the group color).
  const textColor = (typeof window !== 'undefined' && window.contrastInk)
    ? window.contrastInk(fill)
    : '#3a2c3a';

  const W = Math.max(60, group.name.length * 12 + 28);

  // Drag support — live updates with skipHistory, commit once on drag end.
  const moveRef = React.useRef(null);

  const onPointerDown = (e) => {
    if (!updateGroup) return;
    e.stopPropagation();
    moveRef.current = {
      startX: e.clientX, startY: e.clientY,
      ox: offset.dx || 0, oy: offset.dy || 0,
      moved: false,
      last: { dx: offset.dx || 0, dy: offset.dy || 0 },
    };
    try { e.currentTarget.setPointerCapture(e.pointerId); } catch(_){}
  };

  const onPointerMove = (e) => {
    if (!moveRef.current || !updateGroup) return;
    const zoom = worldZoom || 1;
    const dx = (e.clientX - moveRef.current.startX) / zoom;
    const dy = (e.clientY - moveRef.current.startY) / zoom;
    if (Math.abs(dx) + Math.abs(dy) > 2) moveRef.current.moved = true;
    const next = { dx: moveRef.current.ox + dx, dy: moveRef.current.oy + dy };
    moveRef.current.last = next;
    updateGroup(group.id, { labelOffset: next }, { skipHistory: true });
  };

  const onPointerUp = (e) => {
    if (!moveRef.current) return;
    const { moved, last } = moveRef.current;
    moveRef.current = null;
    if (moved && updateGroup) {
      // Commit one history entry representing the whole drag.
      updateGroup(group.id, { labelOffset: last });
    }
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch(_){}
  };

  return (
    <g transform={`translate(${pos.x},${pos.y})`}
       style={{pointerEvents:'auto', cursor:'grab'}}
       onPointerDown={onPointerDown}
       onPointerMove={onPointerMove}
       onPointerUp={onPointerUp}
       onPointerCancel={onPointerUp}>
      <rect x={-W/2} y={-12} width={W} height={24} rx={12}
            fill={fill} stroke="#fff" strokeWidth="3"/>
      <text x={0} y={1}
            textAnchor="middle" dominantBaseline="middle"
            fontFamily='"M PLUS Rounded 1c","Zen Maru Gothic",system-ui,sans-serif'
            fontWeight={800} fontSize={12} fill={textColor}
            style={{pointerEvents:'none', userSelect:'none'}}>
        {group.name}
      </text>
    </g>
  );
}

window.GroupBlob = GroupBlob;
window.GroupLabel = GroupLabel;
