import { layoutAndRoute, netAnchorPoint } from "./layout.js";

const GRID = 20;

const NET_COLORS = {
  power: "#b54708",
  ground: "#344054",
  signal: "#1d4ed8",
  analog: "#0f766e",
  differential: "#c11574",
  clock: "#b93815",
  bus: "#155eef"
};

function esc(text) {
  return String(text)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;");
}

function normalizeRotation(value) {
  const n = Number(value ?? 0);
  if (!Number.isFinite(n)) {
    return 0;
  }
  const snapped = Math.round(n / 90) * 90;
  let rot = snapped % 360;
  if (rot < 0) {
    rot += 360;
  }
  return rot;
}

function rotatePoint(point, center, rotation) {
  if (!rotation) {
    return point;
  }
  const rad = (rotation * Math.PI) / 180;
  const cos = Math.round(Math.cos(rad));
  const sin = Math.round(Math.sin(rad));
  const dx = point.x - center.x;
  const dy = point.y - center.y;
  return {
    x: Math.round(center.x + dx * cos - dy * sin),
    y: Math.round(center.y + dx * sin + dy * cos)
  };
}

function rotateSide(side, rotation) {
  const steps = normalizeRotation(rotation) / 90;
  const order = ["top", "right", "bottom", "left"];
  const idx = order.indexOf(side);
  if (idx < 0) {
    return side;
  }
  return order[(idx + steps) % 4];
}

function truncate(text, max) {
  const s = String(text ?? "");
  if (s.length <= max) {
    return s;
  }
  return `${s.slice(0, Math.max(1, max - 1))}...`;
}

function wrapTextLine(text, maxChars) {
  const words = String(text ?? "").trim().split(/\s+/).filter(Boolean);
  if (!words.length) {
    return [];
  }
  const lines = [];
  let current = "";
  for (const word of words) {
    if (!current) {
      current = word;
      continue;
    }
    if (`${current} ${word}`.length <= maxChars) {
      current = `${current} ${word}`;
      continue;
    }
    lines.push(current);
    current = word;
  }
  if (current) {
    lines.push(current);
  }
  return lines;
}

function netColor(netClass) {
  return NET_COLORS[netClass] ?? NET_COLORS.signal;
}

function symbolTemplateKind(sym) {
  const t = String(sym?.template_name ?? "").toLowerCase();
  if (["resistor", "capacitor", "inductor", "diode", "led", "connector"].includes(t)) {
    return t;
  }
  const c = String(sym?.category ?? "").toLowerCase();
  if (c.includes("resistor")) return "resistor";
  if (c.includes("capacitor")) return "capacitor";
  if (c.includes("inductor")) return "inductor";
  if (c.includes("diode")) return "diode";
  if (c.includes("led")) return "led";
  if (c.includes("connector")) return "connector";
  return null;
}

function renderSymbolBody(sym, x, y, width, height) {
  const kind = symbolTemplateKind(sym);
  if (!kind) {
    return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="8" fill="#ffffff" stroke="#1f2937" stroke-width="2" />`;
  }

  const midX = x + width / 2;
  const midY = y + height / 2;
  const left = x + 16;
  const right = x + width - 16;
  const top = y + 14;
  const bottom = y + height - 14;
  const body = [];
  body.push(`<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="8" fill="#fffdfa" stroke="#1f2937" stroke-width="2" />`);

  if (kind === "resistor") {
    const y0 = midY;
    const pts = [
      [left, y0],
      [left + 16, y0 - 10],
      [left + 28, y0 + 10],
      [left + 40, y0 - 10],
      [left + 52, y0 + 10],
      [left + 64, y0 - 10],
      [right, y0]
    ];
    body.push(`<polyline points="${pts.map((p) => `${p[0]},${p[1]}`).join(" ")}" fill="none" stroke="#344054" stroke-width="2" />`);
  } else if (kind === "capacitor") {
    body.push(`<line x1="${midX - 10}" y1="${top}" x2="${midX - 10}" y2="${bottom}" stroke="#344054" stroke-width="2" />`);
    body.push(`<line x1="${midX + 10}" y1="${top}" x2="${midX + 10}" y2="${bottom}" stroke="#344054" stroke-width="2" />`);
    body.push(`<line x1="${left}" y1="${midY}" x2="${midX - 10}" y2="${midY}" stroke="#344054" stroke-width="2" />`);
    body.push(`<line x1="${midX + 10}" y1="${midY}" x2="${right}" y2="${midY}" stroke="#344054" stroke-width="2" />`);
  } else if (kind === "inductor") {
    body.push(`<line x1="${left}" y1="${midY}" x2="${left + 10}" y2="${midY}" stroke="#344054" stroke-width="2" />`);
    for (let i = 0; i < 4; i += 1) {
      const cx = left + 18 + i * 16;
      body.push(`<path d="M ${cx - 8} ${midY} A 8 8 0 0 1 ${cx + 8} ${midY}" fill="none" stroke="#344054" stroke-width="2" />`);
    }
    body.push(`<line x1="${right - 10}" y1="${midY}" x2="${right}" y2="${midY}" stroke="#344054" stroke-width="2" />`);
  } else if (kind === "diode" || kind === "led") {
    const triLeft = left + 12;
    const triRight = midX + 6;
    body.push(`<line x1="${left}" y1="${midY}" x2="${triLeft}" y2="${midY}" stroke="#344054" stroke-width="2" />`);
    body.push(`<path d="M ${triLeft} ${midY - 14} L ${triLeft} ${midY + 14} L ${triRight} ${midY} Z" fill="none" stroke="#344054" stroke-width="2" />`);
    body.push(`<line x1="${triRight + 4}" y1="${midY - 16}" x2="${triRight + 4}" y2="${midY + 16}" stroke="#344054" stroke-width="2" />`);
    body.push(`<line x1="${triRight + 4}" y1="${midY}" x2="${right}" y2="${midY}" stroke="#344054" stroke-width="2" />`);
    if (kind === "led") {
      body.push(`<line x1="${triRight - 2}" y1="${midY - 20}" x2="${triRight + 8}" y2="${midY - 30}" stroke="#b42318" stroke-width="1.6" />`);
      body.push(`<line x1="${triRight + 10}" y1="${midY - 18}" x2="${triRight + 20}" y2="${midY - 28}" stroke="#b42318" stroke-width="1.6" />`);
    }
  } else if (kind === "connector") {
    body.push(`<line x1="${midX}" y1="${top + 6}" x2="${midX}" y2="${bottom - 6}" stroke="#98a2b3" stroke-width="1.6" />`);
    body.push(`<circle cx="${midX}" cy="${midY - 16}" r="5" fill="#fff" stroke="#344054" stroke-width="1.6" />`);
    body.push(`<circle cx="${midX}" cy="${midY + 16}" r="5" fill="#fff" stroke="#344054" stroke-width="1.6" />`);
  }

  return body.join("");
}

function pinNetMap(model) {
  const map = new Map();
  for (const net of model.nets) {
    for (const node of net.nodes) {
      const key = `${node.ref}.${node.pin}`;
      const list = map.get(key) ?? [];
      list.push(net.name);
      map.set(key, list);
    }
  }
  return map;
}

function renderWirePath(pathD, netName, netClass) {
  const color = netColor(netClass);
  return [
    `<path d="${pathD}" fill="none" stroke="#f8fafc" stroke-width="5" stroke-linecap="round" stroke-linejoin="round" data-net="${esc(netName)}" data-net-class="${esc(netClass)}" />`,
    `<path d="${pathD}" fill="none" stroke="${color}" stroke-width="2.3" stroke-linecap="round" stroke-linejoin="round" data-net="${esc(netName)}" data-net-class="${esc(netClass)}" />`
  ].join("");
}

function renderNetLabel(x, y, netName, netClass, bold = false) {
  const color = netColor(netClass);
  const weight = bold ? "700" : "600";
  return `<text x="${x}" y="${y}" font-size="10" font-weight="${weight}" fill="${color}" stroke="#f8fafc" stroke-width="3" paint-order="stroke fill" data-net-label="${esc(netName)}">${esc(netName)}</text>`;
}

function isGroundLikeNet(net) {
  const cls = String(net?.class ?? "").trim().toLowerCase();
  if (cls === "ground") {
    return true;
  }
  const name = String(net?.name ?? "").trim().toLowerCase();
  return name === "gnd" || name === "ground" || name.endsWith("_gnd");
}

function tieLabelPoint(point, netClass) {
  if (netClass === "power") {
    return { x: point.x + 8, y: point.y - 10 };
  }
  return { x: point.x + 8, y: point.y - 8 };
}

function distance(a, b) {
  return Math.hypot(a.x - b.x, a.y - b.y);
}

function rectFromLabelPoint(point, width = 78, height = 14) {
  return {
    x: point.x - 2,
    y: point.y - height + 2,
    width,
    height
  };
}

function rectsOverlap(a, b) {
  return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
}

function pointInRect(point, rect) {
  return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
}

function withCandidateOffsets(point) {
  const offsets = [
    [0, 0],
    [0, -10],
    [0, 10],
    [10, 0],
    [-10, 0],
    [14, -10],
    [-14, -10],
    [14, 10],
    [-14, 10]
  ];
  return offsets.map(([dx, dy]) => ({ x: point.x + dx, y: point.y + dy }));
}

function pickLabelPoints(points, maxCount, used, usedRects, minSpacing, avoidPoints = [], blockedRects = []) {
  const accepted = [];

  for (const p of points) {
    if (accepted.length >= maxCount) {
      break;
    }

    const candidates = withCandidateOffsets(p);
    for (const candidate of candidates) {
      let blocked = false;
      for (const prev of used) {
        if (distance(candidate, prev) < minSpacing) {
          blocked = true;
          break;
        }
      }
      if (blocked) {
        continue;
      }

      for (const pin of avoidPoints) {
        if (distance(candidate, pin) < minSpacing * 0.9) {
          blocked = true;
          break;
        }
      }
      if (blocked) {
        continue;
      }

      const labelRect = rectFromLabelPoint(candidate);
      for (const usedRect of usedRects) {
        if (rectsOverlap(labelRect, usedRect)) {
          blocked = true;
          break;
        }
      }
      if (blocked) {
        continue;
      }

      for (const blockedRect of blockedRects) {
        if (rectsOverlap(labelRect, blockedRect) || pointInRect(candidate, blockedRect)) {
          blocked = true;
          break;
        }
      }
      if (blocked) {
        continue;
      }

      accepted.push(candidate);
      used.push(candidate);
      usedRects.push(labelRect);
      break;
    }
  }

  return accepted;
}

function placePinText(side, desired, occupiedBySide, minStep) {
  const occupied = occupiedBySide[side] ?? [];
  if (side === "left" || side === "right") {
    let y = desired.y;
    while (occupied.some((value) => Math.abs(value - y) < minStep)) {
      y += minStep;
    }
    occupied.push(y);
    occupiedBySide[side] = occupied;
    return { x: desired.x, y };
  }

  let x = desired.x;
  while (occupied.some((value) => Math.abs(value - x) < minStep)) {
    x += minStep;
  }
  occupied.push(x);
  occupiedBySide[side] = occupied;
  return { x, y: desired.y };
}

function renderGroundSymbol(x, y, netName) {
  const y0 = y + 3;
  const y1 = y + 7;
  const y2 = y + 10;
  const y3 = y + 13;
  return `
<g data-net-tie="${esc(netName)}">
  <line x1="${x}" y1="${y}" x2="${x}" y2="${y0}" stroke="#344054" stroke-width="1.4" />
  <line x1="${x - 6}" y1="${y1}" x2="${x + 6}" y2="${y1}" stroke="#344054" stroke-width="1.4" />
  <line x1="${x - 4}" y1="${y2}" x2="${x + 4}" y2="${y2}" stroke="#344054" stroke-width="1.4" />
  <line x1="${x - 2}" y1="${y3}" x2="${x + 2}" y2="${y3}" stroke="#344054" stroke-width="1.4" />
</g>`;
}

function renderPowerSymbol(x, y, netName) {
  return `
<g data-net-tie="${esc(netName)}">
  <line x1="${x}" y1="${y}" x2="${x}" y2="${y - 4}" stroke="#b54708" stroke-width="1.6" />
  <path d="M ${x - 5} ${y + 2} L ${x} ${y - 8} L ${x + 5} ${y + 2} Z" fill="#b54708" stroke="#f8fafc" stroke-width="1" />
</g>`;
}

function renderGenericTie(x, y, netName, netClass) {
  const color = netColor(netClass);
  return `<circle cx="${x}" cy="${y}" r="3" fill="#ffffff" stroke="${color}" stroke-width="1.3" data-net-tie="${esc(netName)}" />`;
}

function renderTieSymbol(x, y, netName, netClass) {
  if (netClass === "ground") {
    return renderGroundSymbol(x, y, netName);
  }
  if (netClass === "power") {
    return renderPowerSymbol(x, y, netName);
  }
  return renderGenericTie(x, y, netName, netClass);
}

function representativePoint(routeInfo, netAnchor) {
  if (routeInfo.labelPoints?.length) {
    return routeInfo.labelPoints[0];
  }
  if (routeInfo.tiePoints?.length) {
    return routeInfo.tiePoints[0];
  }
  if (routeInfo.routes?.length && routeInfo.routes[0].length) {
    const seg = routeInfo.routes[0][0];
    return {
      x: (seg.a.x + seg.b.x) / 2,
      y: (seg.a.y + seg.b.y) / 2
    };
  }
  return netAnchor;
}

function renderLegend(offsetY = 14) {
  const entries = [
    ["power", "Power"],
    ["ground", "Ground"],
    ["clock", "Clock"],
    ["signal", "Signal"],
    ["analog", "Analog"]
  ];

  const rows = entries
    .map(
      ([cls, label], idx) =>
        `<g transform="translate(0 ${idx * 14})"><line x1="0" y1="6" x2="16" y2="6" stroke="${netColor(cls)}" stroke-width="2" /><text x="22" y="9" fill="#475467" font-size="10">${label}</text></g>`
    )
    .join("");

  return `
<g data-layer="legend" transform="translate(14 ${offsetY})">
  <rect x="-8" y="-8" width="120" height="82" rx="6" fill="#ffffffd8" stroke="#d0d5dd" />
  ${rows}
</g>`;
}

function renderAnnotationPanel(entries, maxWidth = 380) {
  if (!entries.length) {
    return { svg: "", width: 0, height: 0 };
  }

  const panelX = 14;
  const panelY = 14;
  const rowHeight = 14;
  const panelWidth = Math.max(260, Math.min(420, Math.round(maxWidth)));
  const charsPerRow = Math.max(28, Math.floor((panelWidth - 16) / 6.2));
  const wrapped = entries
    .flatMap((line) => wrapTextLine(line, charsPerRow).map((seg) => truncate(seg, charsPerRow)))
    .slice(0, 8);
  const panelHeight = 12 + wrapped.length * rowHeight;
  const textRows = wrapped
    .map(
      (line, idx) =>
        `<text x="${panelX + 8}" y="${panelY + 18 + idx * rowHeight}" font-size="10.5" fill="#6b7280">${esc(line)}</text>`
    )
    .join("\n");

  const svg = `
<g data-layer="annotation-panel">
  <rect x="${panelX}" y="${panelY}" width="${panelWidth}" height="${panelHeight}" rx="6" fill="#ffffffde" stroke="#d0d5dd" />
  ${textRows}
</g>`;

  return { svg, x: panelX, y: panelY, width: panelWidth, height: panelHeight };
}

export function renderSvgFromLayout(model, layout, options = {}) {
  const showLabels = options.show_labels !== false;
  const showAnnotations = options.show_annotations !== false;
  const showLegend = options.show_legend !== false;
  const pinNets = pinNetMap(model);
  const netClassByName = new Map((model.nets ?? []).map((n) => [n.name, n.class]));
  const allPinPoints = [];
  const componentRects = [];

  const components = layout.placed
    .map((inst) => {
      const sym = model.symbols[inst.symbol];
      const x = inst.placement.x;
      const y = inst.placement.y;
      const rotation = normalizeRotation(inst.placement.rotation ?? 0);
      const cx = x + sym.body.width / 2;
      const cy = y + sym.body.height / 2;
      componentRects.push({
        x: x - 6,
        y: y - 6,
        width: sym.body.width + 12,
        height: sym.body.height + 12
      });
      const templateKind = symbolTemplateKind(sym);
      const compactLabel = templateKind || sym.body.width <= 140 || sym.body.height <= 90;
      const legacyShowInstanceNetLabels = Boolean(inst.properties?.show_net_labels);
      const pinUi =
        inst.properties?.pin_ui && typeof inst.properties.pin_ui === "object" && !Array.isArray(inst.properties.pin_ui)
          ? inst.properties.pin_ui
          : {};

      const pinCircles = [];
      const pinLabels = [];
      const instanceNetLabels = [];
      const pinLabelOccupied = { left: [], right: [], top: [], bottom: [] };
      const netLabelOccupied = { left: [], right: [], top: [], bottom: [] };
      for (const pin of sym.pins) {
        let px = x;
        let py = y;

        if (pin.side === "left") {
          px = x;
          py = y + pin.offset;
        } else if (pin.side === "right") {
          px = x + sym.body.width;
          py = y + pin.offset;
        } else if (pin.side === "top") {
          px = x + pin.offset;
          py = y;
        } else {
          px = x + pin.offset;
          py = y + sym.body.height;
        }

        pinCircles.push(
          `<circle cx="${px}" cy="${py}" r="3.2" fill="#111827" data-pin-ref="${esc(inst.ref)}" data-pin-name="${esc(pin.name)}" data-pin-nets="${esc((pinNets.get(`${inst.ref}.${pin.name}`) ?? []).join(","))}" />`
        );

        const rotated = rotatePoint({ x: px, y: py }, { x: cx, y: cy }, rotation);
        const rx = rotated.x;
        const ry = rotated.y;
        const rotatedSide = rotateSide(pin.side, rotation);

        allPinPoints.push({ x: rx, y: ry });
        let labelX = rx + 6;
        let labelY = ry - 4;
        let textAnchor = "start";

        if (rotatedSide === "right") {
          labelX = rx - 6;
          labelY = ry - 4;
          textAnchor = "end";
        } else if (rotatedSide === "top") {
          labelX = rx + 4;
          labelY = ry + 12;
          textAnchor = "start";
        } else if (rotatedSide === "bottom") {
          labelX = rx + 4;
          labelY = ry - 8;
          textAnchor = "start";
        }

        const pinTextPos = placePinText(rotatedSide, { x: labelX, y: labelY }, pinLabelOccupied, 11);
        labelX = pinTextPos.x;
        labelY = pinTextPos.y;

        const showPinLabel = !templateKind || !/^\d+$/.test(pin.name);
        if (showPinLabel) {
          pinLabels.push(
            `<text x="${labelX}" y="${labelY}" text-anchor="${textAnchor}" font-size="10" fill="#475467" data-pin-label="${esc(inst.ref)}.${esc(pin.name)}">${esc(pin.name)}</text>`
          );
        }

        const pinUiEntry = pinUi[pin.name];
        const showPinNetLabel =
          pinUiEntry && typeof pinUiEntry === "object" && Object.prototype.hasOwnProperty.call(pinUiEntry, "show_net_label")
            ? Boolean(pinUiEntry.show_net_label)
            : legacyShowInstanceNetLabels;
        if (showPinNetLabel && showLabels) {
          const nets = pinNets.get(`${inst.ref}.${pin.name}`) ?? [];
          const displayNet = nets.find((n) => !isGroundLikeNet({ name: n, class: "" })) ?? nets[0];
          if (displayNet) {
            let netX = labelX;
            let netY = labelY;
            let netAnchor = textAnchor;

            if (rotatedSide === "left") {
              netX = rx - 12;
              netY = ry - 10;
              netAnchor = "end";
            } else if (rotatedSide === "right") {
              netX = rx + 12;
              netY = ry - 10;
              netAnchor = "start";
            } else if (rotatedSide === "top") {
              netX = rx + 8;
              netY = ry - 10;
              netAnchor = "start";
            } else {
              netX = rx + 8;
              netY = ry + 14;
              netAnchor = "start";
            }

            const netTextPos = placePinText(rotatedSide, { x: netX, y: netY }, netLabelOccupied, 13);
            netX = netTextPos.x;
            netY = netTextPos.y;

            instanceNetLabels.push(
              `<text x="${netX}" y="${netY}" text-anchor="${netAnchor}" font-size="10" font-weight="700" fill="${netColor(netClassByName.get(displayNet))}" stroke="#f8fafc" stroke-width="2.6" paint-order="stroke fill" data-net-label="${esc(displayNet)}" data-ref-net-label="${esc(inst.ref)}">${esc(displayNet)}</text>`
            );
          }
        }
      }

      const pinLabelsSvg = [...pinLabels, ...instanceNetLabels].join("");
      const pinCoreSvg = pinCircles.join("");

      const rotationTransform = rotation ? ` transform="rotate(${rotation} ${cx} ${cy})"` : "";

      const refLabel = truncate(inst.ref, compactLabel ? 6 : 10);
      const valueLabel = truncate(inst.properties?.value ?? inst.symbol, compactLabel ? 18 : 28);
      const refY = compactLabel ? y - 6 : y + 18;
      const valueY = compactLabel ? y + sym.body.height + 14 : y + 34;

      return `
<g data-ref="${esc(inst.ref)}" data-symbol="${esc(inst.symbol)}">
  <g${rotationTransform}>
    ${renderSymbolBody(sym, x, y, sym.body.width, sym.body.height)}
    ${pinCoreSvg}
  </g>
  ${pinLabelsSvg}
  <text x="${x + 8}" y="${refY}" font-size="${compactLabel ? 11 : 12}" font-weight="700" fill="#111827" stroke="#f8fafc" stroke-width="2.2" paint-order="stroke fill" data-ref-label="${esc(inst.ref)}">${esc(refLabel)}</text>
  <text x="${x + 8}" y="${valueY}" font-size="${compactLabel ? 9 : 10}" fill="#667085" stroke="#f8fafc" stroke-width="2" paint-order="stroke fill" data-value-label="${esc(inst.ref)}">${esc(valueLabel)}</text>
</g>`;
    })
    .join("\n");

  const wires = layout.routed
    .flatMap((rn) =>
      rn.routes.map((route) => {
        const path = route
          .map((seg, idx) => `${idx === 0 ? "M" : "L"} ${seg.a.x} ${seg.a.y} L ${seg.b.x} ${seg.b.y}`)
          .join(" ");
        return renderWirePath(path, rn.net.name, rn.net.class);
      })
    )
    .join("\n");

  const junctions = layout.routed
    .flatMap((rn) =>
      (rn.junctionPoints ?? []).map((p) => {
        const color = netColor(rn.net.class);
        return `<circle cx="${p.x}" cy="${p.y}" r="3" fill="${color}" stroke="#f8fafc" stroke-width="1.3" data-net-junction="${esc(rn.net.name)}" />`;
      })
    )
    .join("\n");

  const tiePoints = layout.routed
    .flatMap((rn) =>
      (rn.tiePoints ?? []).map((p) => renderTieSymbol(p.x, p.y, rn.net.name, rn.net.class))
    )
    .join("\n");

  const routedByName = new Map(layout.routed.map((r) => [r.net.name, r]));
  const usedLabelPoints = [];
  const usedLabelRects = [];
  const labels = [];
  const tieLabels = [];
  const annotationLines = showAnnotations
    ? (model.annotations ?? []).map((a) => truncate(String(a.text ?? ""), 140)).filter(Boolean).slice(0, 6)
    : [];
  const annotationPanel = renderAnnotationPanel(annotationLines, (layout.width ?? 1200) * 0.36);
  const legendY = annotationPanel.height > 0 ? annotationPanel.y + annotationPanel.height + 8 : 14;
  const blockedLabelRects = [
    ...(annotationPanel.height > 0
      ? [{ x: annotationPanel.x - 2, y: annotationPanel.y - 2, width: annotationPanel.width + 4, height: annotationPanel.height + 4 }]
      : []),
    ...(showLegend ? [{ x: 6, y: legendY - 8, width: 126, height: 86 }] : []),
    ...componentRects
  ];

  for (const net of model.nets) {
    if (isGroundLikeNet(net)) {
      continue;
    }

    const routeInfo = routedByName.get(net.name);
    if (routeInfo?.isBusMember && routeInfo.mode === "label_tie") {
      continue;
    }

    const netAnchor = netAnchorPoint(net, model, layout.placed);
    const candidates = [];

    if (routeInfo?.mode === "label_tie") {
      candidates.push(...(routeInfo?.labelPoints ?? []));
      const selected = pickLabelPoints(
        candidates,
        1,
        usedLabelPoints,
        usedLabelRects,
        GRID * 2.4,
        allPinPoints,
        blockedLabelRects
      );
      for (const p of selected) {
        labels.push(renderNetLabel(p.x, p.y, net.name, net.class, true));
      }
      continue;
    }

    if (routeInfo?.labelPoints?.length) {
      candidates.push(...routeInfo.labelPoints);
    }
    const routeCenter = representativePoint(routeInfo, netAnchor);
    if (routeCenter) {
      candidates.push({ x: routeCenter.x + 8, y: routeCenter.y - 8 });
    }
    if (netAnchor) {
      candidates.push({ x: netAnchor.x + 8, y: netAnchor.y - 8 });
    }

    const selected = pickLabelPoints(
      candidates,
      1,
      usedLabelPoints,
      usedLabelRects,
      GRID * 2.4,
      allPinPoints,
      blockedLabelRects
    );
    for (const p of selected) {
      labels.push(renderNetLabel(p.x, p.y, net.name, net.class));
    }
  }

  if (showLabels) {
    const usedTieLabels = [];
    const usedTieRects = [];
    for (const rn of layout.routed) {
      if (rn.mode !== "label_tie" || isGroundLikeNet(rn.net)) {
        continue;
      }

      const candidates = (rn.tiePoints ?? []).map((p) => tieLabelPoint(p, rn.net.class));
      if (!candidates.length) {
        continue;
      }

      const maxPerNet = rn.net.class === "power" ? Math.min(6, candidates.length) : Math.min(2, candidates.length);
      const selected = pickLabelPoints(
        candidates,
        maxPerNet,
        usedTieLabels,
        usedTieRects,
        GRID * 1.5,
        allPinPoints,
        blockedLabelRects
      );
      for (const p of selected) {
        tieLabels.push(renderNetLabel(p.x, p.y, rn.net.name, rn.net.class, true));
      }
    }
  }

  const busLabels = (layout.bus_groups ?? [])
    .map((group) => {
      const reps = group.nets
        .map((netName) => {
          const net = model.nets.find((n) => n.name === netName);
          if (!net) {
            return null;
          }
          const anchor = netAnchorPoint(net, model, layout.placed);
          return representativePoint(routedByName.get(netName), anchor);
        })
        .filter(Boolean);

      if (!reps.length) {
        return "";
      }

      const x = reps.reduce((sum, p) => sum + p.x, 0) / reps.length;
      const y = reps.reduce((sum, p) => sum + p.y, 0) / reps.length;

      return `<text x="${x + 12}" y="${y - 12}" font-size="11" font-weight="700" fill="#155eef" stroke="#f8fafc" stroke-width="3" paint-order="stroke fill" data-bus-group="${esc(group.name)}">${esc(group.name)} bus</text>`;
    })
    .join("\n");

  const labelLayer = showLabels ? [...labels, ...tieLabels].join("\n") : "";

  return `<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${layout.width} ${layout.height}" width="${layout.width}" height="${layout.height}" data-engine="schemeta-v2">
  <g data-layer="background">
    <rect x="0" y="0" width="${layout.width}" height="${layout.height}" fill="#f8fafc" />
  </g>
  <g data-layer="components">${components}</g>
  <g data-layer="wires">${wires}</g>
  <g data-layer="junctions">${junctions}</g>
  <g data-layer="ties">${tiePoints}</g>
  <g data-layer="net-labels">${labelLayer}</g>
  <g data-layer="bus-groups">${busLabels}</g>
  ${annotationPanel.svg}
  ${showLegend ? renderLegend(legendY) : ""}
</svg>`;
}

export function renderSvg(model, options = {}) {
  const layout = layoutAndRoute(model, options);
  return renderSvgFromLayout(model, layout, options);
}
