function nodeColor(node) {
  if (node.role === 'me') return '#f59e0b';
  if (node.role === 'target') return '#ef4444';
  if (node.role === 'seed') return '#3b82f6';
  const depth = Math.max(1, Math.min(6, node.depth || 1));
  const palette = ['#10b981', '#22c55e', '#84cc16', '#a3e635', '#bef264', '#d9f99d'];
  return palette[depth - 1] || '#10b981';
}

export function renderGraph({ svg, graph, onNodeClick, showLabels = true }) {
  const width = svg.clientWidth || 900;
  const height = svg.clientHeight || 560;

  svg.innerHTML = '';

  const root = d3.select(svg)
    .attr('viewBox', [0, 0, width, height])
    .attr('width', width)
    .attr('height', height);

  const zoomLayer = root.append('g');

  const link = zoomLayer
    .append('g')
    .attr('stroke', 'var(--border)')
    .attr('stroke-opacity', 0.8)
    .selectAll('line')
    .data(graph.edges)
    .join('line')
    .attr('stroke-width', d => Math.max(1.2, Math.min(4, d.weight || 1)));

  link.append('title').text(d => `Shared works: ${d.weight}`);

  const node = zoomLayer
    .append('g')
    .selectAll('circle')
    .data(graph.nodes)
    .join('circle')
    .attr('r', d => (d.role === 'me' || d.role === 'target' ? 8 : 6))
    .attr('fill', d => nodeColor(d))
    .attr('stroke', 'white')
    .attr('stroke-width', 1.4)
    .style('cursor', 'pointer')
    .on('click', (_, d) => onNodeClick(d));

  node.append('title').text(d => `${d.displayName}\n${d.id}`);

  const labels = zoomLayer
    .append('g')
    .attr('font-size', 11)
    .attr('fill', 'var(--text)')
    .selectAll('text')
    .data(graph.nodes)
    .join('text')
    .text(d => d.displayName)
    .style('display', showLabels ? 'block' : 'none')
    .attr('dx', 9)
    .attr('dy', '0.35em');

  const simulation = d3.forceSimulation(graph.nodes)
    .force('link', d3.forceLink(graph.edges).id(d => d.id).distance(45).strength(0.45))
    .force('charge', d3.forceManyBody().strength(-140))
    .force('center', d3.forceCenter(width / 2, height / 2))
    .force('collision', d3.forceCollide().radius(12));

  simulation.on('tick', () => {
    link
      .attr('x1', d => d.source.x)
      .attr('y1', d => d.source.y)
      .attr('x2', d => d.target.x)
      .attr('y2', d => d.target.y);

    node
      .attr('cx', d => d.x)
      .attr('cy', d => d.y);

    labels
      .attr('x', d => d.x)
      .attr('y', d => d.y);
  });

  node.call(
    d3.drag()
      .on('start', event => {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        event.subject.fx = event.subject.x;
        event.subject.fy = event.subject.y;
      })
      .on('drag', event => {
        event.subject.fx = event.x;
        event.subject.fy = event.y;
      })
      .on('end', event => {
        if (!event.active) simulation.alphaTarget(0);
        event.subject.fx = null;
        event.subject.fy = null;
      })
  );

  root.call(
    d3.zoom()
      .scaleExtent([0.2, 5])
      .on('zoom', event => {
        zoomLayer.attr('transform', event.transform);
      })
  );
}