import { layout } from '../stores/layout.svelte.js';
import { getSymbolImage, isResizable, isCurvedType, isSpurType, isEpcType, isInductionType, isPhotoeyeType, getCurveGeometry, getSymbolGroup, SPACING_EXEMPT, EPC_CONFIG, INDUCTION_CONFIG, PHOTOEYE_CONFIG } from '../symbols.js';
import { checkSpacingViolation } from './collision.js';
import { marqueeRect } from './interactions.js';
import { THEME } from './render-theme.js';
import type { PlacedSymbol } from '../types.js';

let ctx: CanvasRenderingContext2D | null = null;
let canvas: HTMLCanvasElement | null = null;
let animFrameId: number | null = null;
let lastDirty = -1;

const MAX_RENDER_SCALE = THEME.canvas.maxRenderScale;
let currentRenderScale = 0;
let lastCanvasW = 0;
let lastCanvasH = 0;

export function setCanvas(c: HTMLCanvasElement) {
  canvas = c;
  ctx = c.getContext('2d')!;
  currentRenderScale = 0; // force resolution update on first render
}

function updateCanvasResolution() {
  if (!canvas) return;
  const dpr = window.devicePixelRatio || 1;
  const targetScale = Math.min(Math.ceil(dpr * layout.zoomLevel), MAX_RENDER_SCALE);
  const sizeChanged = layout.canvasW !== lastCanvasW || layout.canvasH !== lastCanvasH;
  if (targetScale === currentRenderScale && !sizeChanged) return;
  currentRenderScale = targetScale;
  lastCanvasW = layout.canvasW;
  lastCanvasH = layout.canvasH;
  canvas.width = layout.canvasW * currentRenderScale;
  canvas.height = layout.canvasH * currentRenderScale;
  canvas.style.width = layout.canvasW + 'px';
  canvas.style.height = layout.canvasH + 'px';
}

export function startRenderLoop() {
  function loop() {
    animFrameId = requestAnimationFrame(loop);
    const dpr = window.devicePixelRatio || 1;
    const targetScale = Math.min(Math.ceil(dpr * layout.zoomLevel), MAX_RENDER_SCALE);
    const sizeChanged = layout.canvasW !== lastCanvasW || layout.canvasH !== lastCanvasH;
    if (layout.dirty !== lastDirty || targetScale !== currentRenderScale || sizeChanged) {
      lastDirty = layout.dirty;
      render();
    }
  }
  loop();
}

export function stopRenderLoop() {
  if (animFrameId !== null) {
    cancelAnimationFrame(animFrameId);
    animFrameId = null;
  }
}

export function render() {
  if (!ctx || !canvas) return;

  updateCanvasResolution();
  ctx.save();
  ctx.setTransform(currentRenderScale, 0, 0, currentRenderScale, 0, 0);
  ctx.clearRect(0, 0, layout.canvasW, layout.canvasH);

  if (layout.showGrid) {
    drawGrid(ctx);
  }

  // Draw non-overlay symbols first, then overlay symbols (photoeyes/FIOs) on top
  // Skip hidden symbols (individually hidden or group hidden)
  for (const sym of layout.symbols) {
    if (sym.hidden || layout.hiddenGroups.has(getSymbolGroup(sym.symbolId))) continue;
    if (!SPACING_EXEMPT.has(sym.symbolId)) drawSymbol(ctx, sym as PlacedSymbol);
  }
  for (const sym of layout.symbols) {
    if (sym.hidden || layout.hiddenGroups.has(getSymbolGroup(sym.symbolId))) continue;
    if (SPACING_EXEMPT.has(sym.symbolId)) drawSymbol(ctx, sym as PlacedSymbol);
  }

  // Marquee selection rectangle
  if (marqueeRect) {
    ctx.strokeStyle = '#4a9eff';
    ctx.lineWidth = 1;
    ctx.setLineDash([4, 3]);
    ctx.fillStyle = 'rgba(74, 158, 255, 0.1)';
    ctx.fillRect(marqueeRect.x, marqueeRect.y, marqueeRect.w, marqueeRect.h);
    ctx.strokeRect(marqueeRect.x, marqueeRect.y, marqueeRect.w, marqueeRect.h);
    ctx.setLineDash([]);
  }

  ctx.restore();
}

function drawGrid(ctx: CanvasRenderingContext2D) {
  const size = layout.gridSize;
  ctx.strokeStyle = THEME.grid.color;
  ctx.lineWidth = THEME.grid.lineWidth;
  ctx.beginPath();
  for (let x = 0; x <= layout.canvasW; x += size) {
    ctx.moveTo(x, 0);
    ctx.lineTo(x, layout.canvasH);
  }
  for (let y = 0; y <= layout.canvasH; y += size) {
    ctx.moveTo(0, y);
    ctx.lineTo(layout.canvasW, y);
  }
  ctx.stroke();
}

/** Trace the arc band outline path (for selection/collision/hover strokes on curved types) */
function traceArcBandPath(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, pad: number = 0) {
  const angle = sym.curveAngle || 90;
  const { arcCx, arcCy, outerR, innerR } = getCurveGeometry(sym.symbolId, sym.x, sym.y, sym.w, sym.h);
  const sweepRad = (angle * Math.PI) / 180;

  ctx.beginPath();
  ctx.arc(arcCx, arcCy, outerR + pad, 0, -sweepRad, true);
  ctx.arc(arcCx, arcCy, Math.max(0, innerR - pad), -sweepRad, 0, false);
  ctx.closePath();
}

/** Trace the spur trapezoid path (for selection/collision strokes) */
function traceSpurPath(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, pad: number = 0) {
  const w2 = sym.w2 ?? sym.w;
  // Expand vertices outward by pad
  ctx.beginPath();
  ctx.moveTo(sym.x - pad, sym.y - pad);
  ctx.lineTo(sym.x + w2 + pad, sym.y - pad);
  ctx.lineTo(sym.x + sym.w + pad, sym.y + sym.h + pad);
  ctx.lineTo(sym.x - pad, sym.y + sym.h + pad);
  ctx.closePath();
}

/** Draw the EPC symbol: SVG image for left icon, programmatic polyline + right box.
 *  Both boxes auto-orient along the line direction at their respective ends. */
function drawEpcSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  const waypoints = sym.epcWaypoints || EPC_CONFIG.defaultWaypoints;

  const ox = sym.x; // origin x
  const oy = sym.y; // origin y

  // Draw polyline connecting waypoints
  if (waypoints.length >= 2) {
    ctx.beginPath();
    ctx.moveTo(ox + waypoints[0].x, oy + waypoints[0].y);
    for (let i = 1; i < waypoints.length; i++) {
      ctx.lineTo(ox + waypoints[i].x, oy + waypoints[i].y);
    }
    ctx.strokeStyle = THEME.epcBody.lineColor;
    ctx.lineWidth = EPC_CONFIG.lineWidth;
    ctx.stroke();
  }

  // --- Left icon: use actual SVG image, oriented along first segment ---
  if (waypoints.length >= 2) {
    const lb = EPC_CONFIG.leftBox;
    const p0x = ox + waypoints[0].x, p0y = oy + waypoints[0].y;
    const p1x = ox + waypoints[1].x, p1y = oy + waypoints[1].y;
    const angle = Math.atan2(p1y - p0y, p1x - p0x);

    const iconImg = getSymbolImage(EPC_CONFIG.iconFile);
    if (iconImg) {
      ctx.save();
      ctx.translate(p0x, p0y);
      ctx.rotate(angle);
      ctx.drawImage(iconImg, -lb.w, -lb.h / 2, lb.w, lb.h);
      ctx.restore();
    }
  }

  // --- Right box: oriented along direction from wp[n-2] to wp[n-1] ---
  if (waypoints.length >= 2) {
    const last = waypoints[waypoints.length - 1];
    const prev = waypoints[waypoints.length - 2];
    const plx = ox + last.x, ply = oy + last.y;
    const ppx = ox + prev.x, ppy = oy + prev.y;
    const angle = Math.atan2(ply - ppy, plx - ppx);

    ctx.save();
    ctx.translate(plx, ply);
    ctx.rotate(angle);
    const rb = EPC_CONFIG.rightBox;
    ctx.fillStyle = THEME.epcBody.rightBoxFill;
    ctx.strokeStyle = THEME.epcBody.rightBoxStroke;
    ctx.lineWidth = THEME.epcBody.rightBoxStrokeWidth;
    ctx.rotate(-Math.PI / 2);
    ctx.fillRect(-rb.w, -rb.h / 2, rb.w, rb.h);
    ctx.strokeRect(-rb.w, -rb.h / 2, rb.w, rb.h);
    ctx.restore();
  }
}

/** Draw EPC waypoint handles when selected */
function drawEpcWaypointHandles(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  const waypoints = sym.epcWaypoints || EPC_CONFIG.defaultWaypoints;
  const hs = THEME.epcWaypoint.size;
  ctx.fillStyle = THEME.epcWaypoint.fillColor;
  ctx.strokeStyle = THEME.epcWaypoint.strokeColor;
  ctx.lineWidth = THEME.epcWaypoint.lineWidth;

  for (const wp of waypoints) {
    const hx = sym.x + wp.x;
    const hy = sym.y + wp.y;
    ctx.beginPath();
    ctx.arc(hx, hy, hs / 2, 0, Math.PI * 2);
    ctx.fill();
    ctx.stroke();
  }

  // Draw "+" at midpoints of segments to hint add-waypoint
  if (waypoints.length >= 2) {
    ctx.fillStyle = THEME.epcWaypoint.fillColor;
    ctx.font = THEME.epcWaypoint.hintFont;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    for (let i = 0; i < waypoints.length - 1; i++) {
      const mx = sym.x + (waypoints[i].x + waypoints[i + 1].x) / 2;
      const my = sym.y + (waypoints[i].y + waypoints[i + 1].y) / 2;
      ctx.fillText('+', mx, my + THEME.epcWaypoint.hintOffsetY);
    }
  }
}

/** Trace the EPC outline path: left box + segments + right box */
function traceEpcOutlinePath(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, pad: number) {
  const waypoints = sym.epcWaypoints || EPC_CONFIG.defaultWaypoints;
  if (waypoints.length < 2) return;
  const ox = sym.x, oy = sym.y;

  // Draw left box outline
  const p0x = ox + waypoints[0].x, p0y = oy + waypoints[0].y;
  const p1x = ox + waypoints[1].x, p1y = oy + waypoints[1].y;
  const lAngle = Math.atan2(p1y - p0y, p1x - p0x);
  const lb = EPC_CONFIG.leftBox;

  ctx.save();
  ctx.translate(p0x, p0y);
  ctx.rotate(lAngle);
  ctx.beginPath();
  ctx.rect(-lb.w - pad, -lb.h / 2 - pad, lb.w + pad * 2, lb.h + pad * 2);
  ctx.stroke();
  ctx.restore();

  // Draw line segments outline (thickened)
  for (let i = 0; i < waypoints.length - 1; i++) {
    const ax = ox + waypoints[i].x, ay = oy + waypoints[i].y;
    const bx = ox + waypoints[i + 1].x, by = oy + waypoints[i + 1].y;
    const segAngle = Math.atan2(by - ay, bx - ax);
    const segLen = Math.sqrt((bx - ax) ** 2 + (by - ay) ** 2);
    ctx.save();
    ctx.translate(ax, ay);
    ctx.rotate(segAngle);
    ctx.beginPath();
    ctx.rect(-pad, -pad - EPC_CONFIG.lineWidth / 2, segLen + pad * 2, EPC_CONFIG.lineWidth + pad * 2);
    ctx.stroke();
    ctx.restore();
  }

  // Draw right box outline
  const last = waypoints[waypoints.length - 1];
  const prev = waypoints[waypoints.length - 2];
  const plx = ox + last.x, ply = oy + last.y;
  const ppx = ox + prev.x, ppy = oy + prev.y;
  const rAngle = Math.atan2(ply - ppy, plx - ppx);
  const rb = EPC_CONFIG.rightBox;

  ctx.save();
  ctx.translate(plx, ply);
  ctx.rotate(rAngle - Math.PI / 2);
  ctx.beginPath();
  ctx.rect(-rb.w - pad, -rb.h / 2 - pad, rb.w + pad * 2, rb.h + pad * 2);
  ctx.stroke();
  ctx.restore();
}

/** Trace the induction outline path (arrow head + strip) */
function traceInductionPath(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, pad: number) {
  const hw = INDUCTION_CONFIG.headWidth;
  const stripTopY = sym.y + sym.h * INDUCTION_CONFIG.stripTopFrac;
  const stripBottomY = sym.y + sym.h * INDUCTION_CONFIG.stripBottomFrac;
  const pts = INDUCTION_CONFIG.arrowPoints.map(([xf, yf]) => [sym.x + xf * hw, sym.y + yf * sym.h] as const);

  ctx.beginPath();
  ctx.moveTo(sym.x + sym.w + pad, stripTopY - pad);
  ctx.lineTo(pts[0][0], stripTopY - pad);
  // Arrow outline with padding
  for (let i = 0; i < pts.length; i++) {
    const [px, py] = pts[i];
    // Simple approach: offset each point outward by pad
    ctx.lineTo(px + (i <= 2 ? -pad : pad), py + (i <= 1 ? -pad : pad));
  }
  ctx.lineTo(pts[5][0], stripBottomY + pad);
  ctx.lineTo(sym.x + sym.w + pad, stripBottomY + pad);
  ctx.closePath();
}

/** Stroke an outline around a symbol — uses arc path for curved, trapezoid for spur, EPC shape for EPC, induction shape for induction, rect for straight */
function strokeOutline(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, pad: number) {
  if (isCurvedType(sym.symbolId)) {
    traceArcBandPath(ctx, sym, pad);
    ctx.stroke();
  } else if (isSpurType(sym.symbolId)) {
    traceSpurPath(ctx, sym, pad);
    ctx.stroke();
  } else if (isEpcType(sym.symbolId)) {
    traceEpcOutlinePath(ctx, sym, pad);
  } else if (isInductionType(sym.symbolId)) {
    traceInductionPath(ctx, sym, pad);
    ctx.stroke();
  } else {
    ctx.strokeRect(sym.x - pad, sym.y - pad, sym.w + pad * 2, sym.h + pad * 2);
  }
}

/** Draw a filled+stroked resize handle at (x, y) */
function drawHandle(ctx: CanvasRenderingContext2D, x: number, y: number, size: number) {
  const half = size / 2;
  ctx.fillRect(x - half, y - half, size, size);
  ctx.strokeRect(x - half, y - half, size, size);
}

function drawResizeHandles(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  if (!isResizable(sym.symbolId)) return;
  const hs = THEME.resizeHandle.size;
  ctx.fillStyle = THEME.resizeHandle.fillColor;
  ctx.strokeStyle = THEME.resizeHandle.strokeColor;
  ctx.lineWidth = THEME.resizeHandle.lineWidth;

  if (isCurvedType(sym.symbolId)) {
    const { arcCx, arcCy, outerR } = getCurveGeometry(sym.symbolId, sym.x, sym.y, sym.w, sym.h);
    drawHandle(ctx, arcCx + outerR, arcCy, hs);
  } else if (isSpurType(sym.symbolId)) {
    const w2 = sym.w2 ?? sym.w;
    // Right handle on top base (controls w2)
    drawHandle(ctx, sym.x + w2, sym.y, hs);
    // Right handle on bottom base (controls w)
    drawHandle(ctx, sym.x + sym.w, sym.y + sym.h, hs);
  } else if (isInductionType(sym.symbolId)) {
    // Only right handle — arrow head is fixed width
    const stripTopY = sym.y + sym.h * INDUCTION_CONFIG.stripTopFrac;
    const stripBottomY = sym.y + sym.h * INDUCTION_CONFIG.stripBottomFrac;
    const stripMidY = (stripTopY + stripBottomY) / 2;
    drawHandle(ctx, sym.x + sym.w, stripMidY, hs);
  } else {
    const midY = sym.y + sym.h / 2;
    drawHandle(ctx, sym.x, midY, hs);
    drawHandle(ctx, sym.x + sym.w, midY, hs);
  }
}

/** Draw induction programmatically: fixed arrow head + variable strip, as one path */
function drawInductionSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  const hw = INDUCTION_CONFIG.headWidth;
  const stripTopY = sym.y + sym.h * INDUCTION_CONFIG.stripTopFrac;
  const stripBottomY = sym.y + sym.h * INDUCTION_CONFIG.stripBottomFrac;

  // Arrow points in display coords
  const pts = INDUCTION_CONFIG.arrowPoints.map(([xf, yf]) => [sym.x + xf * hw, sym.y + yf * sym.h] as const);

  ctx.beginPath();
  // Top-right of strip
  ctx.moveTo(sym.x + sym.w, stripTopY);
  // Top-left junction (arrow meets strip)
  ctx.lineTo(pts[0][0], stripTopY);
  // Arrow outline
  for (const [px, py] of pts) {
    ctx.lineTo(px, py);
  }
  // Bottom-left junction to strip bottom
  ctx.lineTo(pts[5][0], stripBottomY);
  // Bottom-right of strip
  ctx.lineTo(sym.x + sym.w, stripBottomY);
  ctx.closePath();

  ctx.fillStyle = THEME.induction.fillColor;
  ctx.fill();
}

/** Draw photoeye with 3-slice: fixed left cap, stretched middle beam, fixed right cap */
function drawPhotoeye3Slice(ctx: CanvasRenderingContext2D, sym: PlacedSymbol, img: HTMLImageElement) {
  const { leftCap, rightCap, defaultWidth } = PHOTOEYE_CONFIG;
  const srcW = img.naturalWidth;
  const srcH = img.naturalHeight;
  const scale = srcW / defaultWidth;
  const srcLeftW = leftCap * scale;
  const srcRightW = rightCap * scale;
  const srcMiddleW = srcW - srcLeftW - srcRightW;
  const dstMiddleW = sym.w - leftCap - rightCap;

  // Left cap (fixed)
  ctx.drawImage(img, 0, 0, srcLeftW, srcH, sym.x, sym.y, leftCap, sym.h);
  // Middle beam (stretched)
  ctx.drawImage(img, srcLeftW, 0, srcMiddleW, srcH, sym.x + leftCap, sym.y, dstMiddleW, sym.h);
  // Right cap (fixed)
  ctx.drawImage(img, srcW - srcRightW, 0, srcRightW, srcH, sym.x + sym.w - rightCap, sym.y, rightCap, sym.h);
}

/** Draw curved conveyor/chute programmatically with fixed band width */
function drawCurvedSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  const angle = sym.curveAngle || 90;
  const { arcCx, arcCy, outerR, innerR } = getCurveGeometry(sym.symbolId, sym.x, sym.y, sym.w, sym.h);
  const sweepRad = (angle * Math.PI) / 180;

  ctx.beginPath();
  ctx.arc(arcCx, arcCy, outerR, 0, -sweepRad, true);
  ctx.arc(arcCx, arcCy, innerR, -sweepRad, 0, false);
  ctx.closePath();

  ctx.fillStyle = '#000000';
  ctx.strokeStyle = '#000000';
  ctx.lineWidth = 0.5;
  ctx.fill();
  ctx.stroke();
}

function drawSymbolBody(ctx: CanvasRenderingContext2D, sym: PlacedSymbol): boolean {
  if (isEpcType(sym.symbolId)) {
    drawEpcSymbol(ctx, sym);
  } else if (isInductionType(sym.symbolId)) {
    drawInductionSymbol(ctx, sym);
  } else if (isCurvedType(sym.symbolId)) {
    drawCurvedSymbol(ctx, sym);
  } else if (isSpurType(sym.symbolId)) {
    // Draw trapezoid programmatically so w and w2 are respected during resize
    const w2 = sym.w2 ?? sym.w;
    ctx.beginPath();
    ctx.moveTo(sym.x, sym.y);
    ctx.lineTo(sym.x + w2, sym.y);
    ctx.lineTo(sym.x + sym.w, sym.y + sym.h);
    ctx.lineTo(sym.x, sym.y + sym.h);
    ctx.closePath();
    ctx.fillStyle = '#000000';
    ctx.strokeStyle = '#000000';
    ctx.lineWidth = 0.5;
    ctx.fill();
    ctx.stroke();
  } else {
    const img = getSymbolImage(sym.file);
    if (!img) return false;
    if (isPhotoeyeType(sym.symbolId)) {
      drawPhotoeye3Slice(ctx, sym, img);
    } else {
      ctx.drawImage(img, sym.x, sym.y, sym.w, sym.h);
    }
  }
  return true;
}

function drawSymbolOverlays(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  const cx = sym.x + sym.w / 2;
  const isSelected = layout.selectedIds.has(sym.id);

  // Selection highlight
  if (isSelected) {
    ctx.strokeStyle = THEME.selection.strokeColor;
    ctx.lineWidth = THEME.selection.lineWidth;
    ctx.shadowColor = THEME.selection.shadowColor;
    ctx.shadowBlur = THEME.selection.shadowBlur;
    strokeOutline(ctx, sym, THEME.selection.pad);
    ctx.shadowBlur = 0;
    if (layout.selectedIds.size === 1) {
      if (isEpcType(sym.symbolId)) {
        drawEpcWaypointHandles(ctx, sym);
      } else {
        drawResizeHandles(ctx, sym);
      }
    }
  }

  // Collision highlight
  if (checkSpacingViolation(sym.id, sym.x, sym.y, sym.w, sym.h, sym.rotation, sym.symbolId, sym.curveAngle, sym.w2, sym.mirrored)) {
    ctx.strokeStyle = THEME.collision.strokeColor;
    ctx.lineWidth = THEME.collision.lineWidth;
    ctx.shadowColor = THEME.collision.shadowColor;
    ctx.shadowBlur = THEME.collision.shadowBlur;
    strokeOutline(ctx, sym, THEME.collision.pad);
    ctx.shadowBlur = 0;
  }

  // Label drop-target highlight
  if (layout.labelDropTarget === sym.id) {
    ctx.fillStyle = THEME.dropTarget.fillColor;
    ctx.strokeStyle = THEME.dropTarget.strokeColor;
    ctx.lineWidth = THEME.dropTarget.lineWidth;
    ctx.shadowColor = THEME.dropTarget.shadowColor;
    ctx.shadowBlur = THEME.dropTarget.shadowBlur;
    // Fill + stroke the outline
    ctx.beginPath();
    ctx.rect(sym.x - THEME.dropTarget.pad, sym.y - THEME.dropTarget.pad,
             sym.w + THEME.dropTarget.pad * 2, sym.h + THEME.dropTarget.pad * 2);
    ctx.fill();
    ctx.stroke();
    ctx.shadowBlur = 0;
  }

  // Hover border (non-selected)
  if (!isSelected && layout.labelDropTarget !== sym.id) {
    ctx.strokeStyle = THEME.hover.strokeColor;
    ctx.lineWidth = THEME.hover.lineWidth;
    strokeOutline(ctx, sym, 0);
  }

  // Label: centered inside symbol if large enough, above if too small
  if (sym.label) {
    ctx.fillStyle = THEME.label.color;
    ctx.font = THEME.label.font;
    const metrics = ctx.measureText(sym.label);
    const textH = 3; // approximate font height
    if (sym.w >= metrics.width + 4 && sym.h >= textH + 4) {
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(sym.label, cx, sym.y + sym.h / 2);
    } else {
      ctx.textAlign = 'center';
      ctx.textBaseline = 'bottom';
      ctx.fillText(sym.label, cx, sym.y + THEME.label.offsetY);
    }
  }
}

function drawSymbol(ctx: CanvasRenderingContext2D, sym: PlacedSymbol) {
  ctx.save();

  // Apply rotation and mirror transforms
  if (sym.rotation || sym.mirrored) {
    const cx = sym.x + sym.w / 2;
    const cy = sym.y + sym.h / 2;
    ctx.translate(cx, cy);
    if (sym.rotation) ctx.rotate((sym.rotation * Math.PI) / 180);
    if (sym.mirrored) ctx.scale(-1, 1);
    ctx.translate(-cx, -cy);
  }

  if (!drawSymbolBody(ctx, sym)) {
    ctx.restore();
    return;
  }

  drawSymbolOverlays(ctx, sym);
  ctx.restore();
}
