import { createLogger } from '@sim/logger'

const logger = createLogger('PptxShapePresets')

/**
 * Preset shape SVG path generators for OOXML preset geometry types.
 *
 * Each generator takes width, height, and optional adjustment values,
 * returning an SVG path d-attribute string.
 *
 * Adjustment values follow OOXML convention: values are in 100000ths
 * (so 50000 = 50%).
 */

import { shapeArc } from './shape-arc'

type PresetShapeGenerator = (w: number, h: number, adjustments?: Map<string, number>) => string

/** Helper: get adjustment value or default, converting from 100000ths to fraction. */
function adj(
  adjustments: Map<string, number> | undefined,
  name: string,
  defaultVal: number
): number {
  const raw = adjustments?.get(name) ?? defaultVal
  return raw / 100000
}

/** Helper: generate a regular polygon path (inscribed in bounding box). */
function _regularPolygon(w: number, h: number, sides: number): string {
  const cx = w / 2
  const cy = h / 2
  const rx = w / 2
  const ry = h / 2
  const parts: string[] = []
  for (let i = 0; i < sides; i++) {
    // Start from top center (-90 degrees)
    const angle = (2 * Math.PI * i) / sides - Math.PI / 2
    const x = cx + rx * Math.cos(angle)
    const y = cy + ry * Math.sin(angle)
    parts.push(i === 0 ? `M${x},${y}` : `L${x},${y}`)
  }
  parts.push('Z')
  return parts.join(' ')
}

/** Raw adj helper: get adjustment value without dividing by 100000. */
function adjRaw(
  adjustments: Map<string, number> | undefined,
  name: string,
  defaultVal: number
): number {
  return adjustments?.get(name) ?? defaultVal
}

/** Helper: generate a star polygon. */
function starShape(w: number, h: number, points: number, innerRatio = 0.4): string {
  const cx = w / 2
  const cy = h / 2
  const outerRx = w / 2
  const outerRy = h / 2
  const innerRx = outerRx * innerRatio
  const innerRy = outerRy * innerRatio
  const totalPoints = points * 2
  const parts: string[] = []

  for (let i = 0; i < totalPoints; i++) {
    const angle = (2 * Math.PI * i) / totalPoints - Math.PI / 2
    const isOuter = i % 2 === 0
    const rx = isOuter ? outerRx : innerRx
    const ry = isOuter ? outerRy : innerRy
    const x = cx + rx * Math.cos(angle)
    const y = cy + ry * Math.sin(angle)
    parts.push(i === 0 ? `M${x},${y}` : `L${x},${y}`)
  }
  parts.push('Z')
  return parts.join(' ')
}

/**
 * Mirror an absolute SVG path horizontally across the given width.
 * Supports the command subset used by preset arrow shapes: M, L, A, Z.
 */
function mirrorAbsolutePathHorizontally(path: string, width: number): string {
  const tokens = path.match(/[MLAZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi)
  if (!tokens) return path

  const out: string[] = []
  let i = 0
  while (i < tokens.length) {
    const cmd = tokens[i++]
    if (!cmd) break
    out.push(cmd)
    if (cmd === 'Z') continue
    if (cmd === 'M' || cmd === 'L') {
      const x = Number(tokens[i++])
      const y = Number(tokens[i++])
      out.push(String(width - x), String(y))
      continue
    }
    if (cmd === 'A') {
      const rx = tokens[i++]
      const ry = tokens[i++]
      const rot = tokens[i++]
      const largeArc = tokens[i++]
      const sweep = Number(tokens[i++])
      const x = Number(tokens[i++])
      const y = Number(tokens[i++])
      out.push(rx, ry, rot, largeArc, String(sweep ? 0 : 1), String(width - x), String(y))
      continue
    }
    return path
  }

  return out.join(' ')
}

function mirrorAbsolutePathVertically(path: string, height: number): string {
  const tokens = path.match(/[MLAZ]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi)
  if (!tokens) return path

  const out: string[] = []
  let i = 0
  while (i < tokens.length) {
    const cmd = tokens[i++]
    if (!cmd) break
    out.push(cmd)
    if (cmd === 'Z') continue
    if (cmd === 'M' || cmd === 'L') {
      const x = Number(tokens[i++])
      const y = Number(tokens[i++])
      out.push(String(x), String(height - y))
      continue
    }
    if (cmd === 'A') {
      const rx = tokens[i++]
      const ry = tokens[i++]
      const rot = tokens[i++]
      const largeArc = tokens[i++]
      const sweep = Number(tokens[i++])
      const x = Number(tokens[i++])
      const y = Number(tokens[i++])
      out.push(rx, ry, rot, largeArc, String(sweep ? 0 : 1), String(x), String(height - y))
    }
  }
  return out.join(' ')
}

// ---------------------------------------------------------------------------
// Preset shape registry
// ---------------------------------------------------------------------------

export const presetShapes: Map<string, PresetShapeGenerator> = new Map()

// ===== Basic Shapes =====

presetShapes.set('rect', (w, h) => `M0,0 L${w},0 L${w},${h} L0,${h} Z`)

presetShapes.set('roundRect', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 16667)
  const r = Math.min(w, h) * a
  return [
    `M${r},0`,
    `L${w - r},0`,
    `A${r},${r} 0 0,1 ${w},${r}`,
    `L${w},${h - r}`,
    `A${r},${r} 0 0,1 ${w - r},${h}`,
    `L${r},${h}`,
    `A${r},${r} 0 0,1 0,${h - r}`,
    `L0,${r}`,
    `A${r},${r} 0 0,1 ${r},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('plaque', (w, h, adjustments) => {
  // OOXML: adj default 16667, concave (inward) arc corners via negative sweep arcTo
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 16667), 0), 50000)
  const x1 = (Math.min(w, h) * a) / 100000
  const x2 = w - x1
  const y2 = h - x1
  // Start at (0, x1), arcTo with negative sweep creates concave corner
  const a1 = ooArcTo(0, x1, x1, x1, 90, -90) // top-left: ends at (x1, 0)
  const a2 = ooArcTo(x2, 0, x1, x1, 180, -90) // top-right: ends at (w, x1)
  const a3 = ooArcTo(w, y2, x1, x1, 270, -90) // bottom-right: ends at (x2, h)
  const a4 = ooArcTo(x1, h, x1, x1, 0, -90) // bottom-left: ends at (0, y2) -> close to (0, x1)
  return [
    `M0,${x1}`,
    a1.svg,
    `L${x2},0`,
    a2.svg,
    `L${w},${y2}`,
    a3.svg,
    `L${x1},${h}`,
    a4.svg,
    'Z',
  ].join(' ')
})

// Tab family: OOXML uses dx = sqrt(w²+h²)/20 (diagonal/20)
presetShapes.set('cornerTabs', (w, h) => {
  const dx = Math.sqrt(w * w + h * h) / 20
  return [
    `M0,0 L${dx},0 L0,${dx} Z`,
    `M${w},0 L${w - dx},0 L${w},${dx} Z`,
    `M${w},${h} L${w - dx},${h} L${w},${h - dx} Z`,
    `M0,${h} L${dx},${h} L0,${h - dx} Z`,
  ].join(' ')
})

presetShapes.set('squareTabs', (w, h) => {
  const dx = Math.sqrt(w * w + h * h) / 20
  return [
    `M0,0 L${dx},0 L${dx},${dx} L0,${dx} Z`,
    `M${w - dx},0 L${w},0 L${w},${dx} L${w - dx},${dx} Z`,
    `M0,${h - dx} L${dx},${h - dx} L${dx},${h} L0,${h} Z`,
    `M${w - dx},${h - dx} L${w},${h - dx} L${w},${h} L${w - dx},${h} Z`,
  ].join(' ')
})

presetShapes.set('plaqueTabs', (w, h) => {
  const dx = Math.sqrt(w * w + h * h) / 20
  return [
    `M0,0 L${dx},0 A${dx},${dx} 0 0,1 0,${dx} Z`,
    `M${w},0 L${w - dx},0 A${dx},${dx} 0 0,0 ${w},${dx} Z`,
    `M0,${h} L0,${h - dx} A${dx},${dx} 0 0,1 ${dx},${h} Z`,
    `M${w},${h} L${w - dx},${h} A${dx},${dx} 0 0,1 ${w},${h - dx} Z`,
  ].join(' ')
})

presetShapes.set('ellipse', (w, h) => {
  const rx = w / 2
  const ry = h / 2
  return [`M${w},${ry}`, `A${rx},${ry} 0 1,1 0,${ry}`, `A${rx},${ry} 0 1,1 ${w},${ry}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('triangle', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 50000)
  const topX = w * a
  return `M${topX},0 L${w},${h} L0,${h} Z`
})

presetShapes.set('isosTriangle', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 50000)
  const topX = w * a
  return `M${topX},0 L${w},${h} L0,${h} Z`
})

presetShapes.set('rtTriangle', (w, h) => `M0,0 L${w},${h} L0,${h} Z`)

presetShapes.set('diamond', (w, h) => {
  const cx = w / 2
  const cy = h / 2
  return `M${cx},0 L${w},${cy} L${cx},${h} L0,${cy} Z`
})

presetShapes.set('pentagon', (w, h) => {
  // OOXML pentagon: hf=105146, vf=110557 with center shifted to svc so top vertex = y=0.
  const hc = w / 2
  const swd2 = (hc * 105146) / 100000
  const shd2 = ((h / 2) * 110557) / 100000
  const svc = shd2 // svc = vc * vf/100000 = shd2, so top vertex at svc - shd2 = 0
  const dx1 = swd2 * Math.cos((18 * Math.PI) / 180) // cos 1080000
  const dx2 = swd2 * Math.cos((54 * Math.PI) / 180) // cos 18360000
  const dy1 = shd2 * Math.sin((18 * Math.PI) / 180) // sin 1080000
  const dy2 = shd2 * Math.sin((54 * Math.PI) / 180) // |sin 18360000|
  return [
    `M${hc - dx1},${svc - dy1}`, // x1, y1 (upper-left)
    `L${hc},0`, // hc, t (top)
    `L${hc + dx1},${svc - dy1}`, // x4, y1 (upper-right)
    `L${hc + dx2},${svc + dy2}`, // x3, y2 (lower-right)
    `L${hc - dx2},${svc + dy2}`, // x2, y2 (lower-left)
    'Z',
  ].join(' ')
})

presetShapes.set('hexagon', (w, h, adjustments) => {
  // OOXML hexagon: adj=25000, vf=115470 (2/√3 scale factor for regular hex).
  const ss = Math.min(w, h)
  const a = Math.min(
    Math.max(adjRaw(adjustments, 'adj', 25000), 0),
    ss > 0 ? (50000 * w) / ss : 50000
  )
  const vf = 115470
  const shd2 = ((h / 2) * vf) / 100000
  const x1 = (ss * a) / 100000
  const x2 = w - x1
  const _hc = w / 2
  const vc = h / 2
  // dy1 = sin(shd2, 60°) = shd2 * sin(60°)
  const dy1 = shd2 * Math.sin((60 * Math.PI) / 180)
  const y1 = vc - dy1
  const y2 = vc + dy1
  return [
    `M0,${vc}`,
    `L${x1},${y1}`,
    `L${x2},${y1}`,
    `L${w},${vc}`,
    `L${x2},${y2}`,
    `L${x1},${y2}`,
    'Z',
  ].join(' ')
})

presetShapes.set('octagon', (w, h, adjustments) => {
  // OOXML octagon: adj=29289 (≈1-1/√2). Uses ss-based cuts for both x and y.
  const ss = Math.min(w, h)
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 29289), 0), 50000)
  const x1 = (ss * a) / 100000
  const x2 = w - x1
  const y2 = h - x1
  return [
    `M0,${x1}`,
    `L${x1},0`,
    `L${x2},0`,
    `L${w},${x1}`,
    `L${w},${y2}`,
    `L${x2},${h}`,
    `L${x1},${h}`,
    `L0,${y2}`,
    'Z',
  ].join(' ')
})

presetShapes.set('heptagon', (w, h) => {
  // OOXML heptagon: hf=102572, vf=105210 with shifted center.
  const hc = w / 2
  const swd2 = (hc * 102572) / 100000
  const shd2 = ((h / 2) * 105210) / 100000
  const svc = ((h / 2) * 105210) / 100000
  // Pre-computed trig ratios from OOXML spec (scaled by 100000)
  const dx1 = (swd2 * 97493) / 100000 // cos(12.857°) ≈ sin(77.14°)
  const dx2 = (swd2 * 78183) / 100000 // cos(38.57°)
  const dx3 = (swd2 * 43388) / 100000 // cos(64.29°)
  const dy1 = (shd2 * 62349) / 100000 // sin(38.57°)
  const dy2 = (shd2 * 22252) / 100000 // sin(12.857°)
  const dy3 = (shd2 * 90097) / 100000 // sin(64.29°)
  return [
    `M${hc - dx1},${svc + dy2}`, // x1, y2 (left)
    `L${hc - dx2},${svc - dy1}`, // x2, y1 (upper-left)
    `L${hc},0`, // hc, t (top: svc - shd2 = 0)
    `L${hc + dx2},${svc - dy1}`, // x5, y1 (upper-right)
    `L${hc + dx1},${svc + dy2}`, // x6, y2 (right)
    `L${hc + dx3},${svc + dy3}`, // x4, y3 (lower-right)
    `L${hc - dx3},${svc + dy3}`, // x3, y3 (lower-left)
    'Z',
  ].join(' ')
})
presetShapes.set('decagon', (w, h) => {
  // OOXML decagon: vf=105146 (no hf, uses wd2 for x). 10 vertices starting from left.
  const hc = w / 2
  const vc = h / 2
  const shd2 = (vc * 105146) / 100000
  // OOXML angles: 2160000=36°, 4320000=72°
  const dx1 = hc * Math.cos((36 * Math.PI) / 180) // cos(wd2, 2160000)
  const dx2 = hc * Math.cos((72 * Math.PI) / 180) // cos(wd2, 4320000)
  const dy1 = shd2 * Math.sin((72 * Math.PI) / 180) // sin(shd2, 4320000)
  const dy2 = shd2 * Math.sin((36 * Math.PI) / 180) // sin(shd2, 2160000)
  return [
    `M0,${vc}`, // l, vc
    `L${hc - dx1},${vc - dy2}`, // x1, y2
    `L${hc - dx2},${vc - dy1}`, // x2, y1
    `L${hc + dx2},${vc - dy1}`, // x3, y1
    `L${hc + dx1},${vc - dy2}`, // x4, y2
    `L${w},${vc}`, // r, vc
    `L${hc + dx1},${vc + dy2}`, // x4, y3
    `L${hc + dx2},${vc + dy1}`, // x3, y4
    `L${hc - dx2},${vc + dy1}`, // x2, y4
    `L${hc - dx1},${vc + dy2}`, // x1, y3
    'Z',
  ].join(' ')
})
presetShapes.set('dodecagon', (w, h) => {
  // OOXML dodecagon: 21600-unit coordinate space, simple ratios.
  const x1 = (w * 2894) / 21600
  const x2 = (w * 7906) / 21600
  const x3 = (w * 13694) / 21600
  const x4 = (w * 18706) / 21600
  const y1 = (h * 2894) / 21600
  const y2 = (h * 7906) / 21600
  const y3 = (h * 13694) / 21600
  const y4 = (h * 18706) / 21600
  return [
    `M0,${y2}`,
    `L${x1},${y1}`,
    `L${x2},0`,
    `L${x3},0`,
    `L${x4},${y1}`,
    `L${w},${y2}`,
    `L${w},${y3}`,
    `L${x4},${y4}`,
    `L${x3},${h}`,
    `L${x2},${h}`,
    `L${x1},${y4}`,
    `L0,${y3}`,
    'Z',
  ].join(' ')
})

presetShapes.set('parallelogram', (w, h, adjustments) => {
  // OOXML: adj=25000, x2 = ss * a / 100000, path: M(l,b)→L(x2,t)→L(r,t)→L(r-x2,b)→Z
  const ss = Math.min(w, h)
  const maxAdj = ss > 0 ? (100000 * w) / ss : 100000
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 25000), 0), maxAdj)
  const x2 = (ss * a) / 100000
  const x5 = w - x2
  return `M0,${h} L${x2},0 L${w},0 L${x5},${h} Z`
})

presetShapes.set('trapezoid', (w, h, adjustments) => {
  // OOXML: adj=25000, x2 = ss * a / 100000, x3 = r - x2
  const ss = Math.min(w, h)
  const maxAdj = ss > 0 ? (50000 * w) / ss : 50000
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 25000), 0), maxAdj)
  const x2 = (ss * a) / 100000
  const x3 = w - x2
  return `M0,${h} L${x2},0 L${x3},0 L${w},${h} Z`
})

presetShapes.set('nonIsoscelesTrapezoid', (w, h, adjustments) => {
  // OOXML: Two independent top insets. adj1=25000, adj2=25000
  const ss = Math.min(w, h)
  const maxAdj = ss > 0 ? (50000 * w) / ss : 50000
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj1', 25000), 0), maxAdj)
  const a2 = Math.min(Math.max(adjRaw(adjustments, 'adj2', 25000), 0), maxAdj)
  const x2 = (ss * a1) / 100000
  const dx3 = (ss * a2) / 100000
  const x3 = w - dx3
  return `M0,${h} L${x2},0 L${x3},0 L${w},${h} Z`
})

presetShapes.set('corner', (w, h, adjustments) => {
  // OOXML corner: two adjustments control horizontal and vertical arm thickness.
  // adj1 (default 50000) → vertical arm height from bottom: dy1 = ss * a1, y1 = h - dy1
  // adj2 (default 50000) → horizontal arm width from left: x1 = ss * a2
  const ss = Math.min(w, h)
  const a1 = Math.min(Math.max(adj(adjustments, 'adj1', 50000), 0), 1)
  const a2 = Math.min(Math.max(adj(adjustments, 'adj2', 50000), 0), 1)
  const x1 = ss * a2
  const dy1 = ss * a1
  const y1 = h - dy1
  return [`M0,0`, `L${x1},0`, `L${x1},${y1}`, `L${w},${y1}`, `L${w},${h}`, `L0,${h}`, 'Z'].join(' ')
})

presetShapes.set('diagStripe', (w, h, adjustments) => {
  const a = Math.min(Math.max(adj(adjustments, 'adj', 50000), 0), 1)
  const x2 = w * a
  const y2 = h * a
  return [`M0,${y2}`, `L${x2},0`, `L${w},0`, `L0,${h}`, 'Z'].join(' ')
})

// ===== Star Shapes =====

presetShapes.set('star4', (w, h, adjustments) => {
  // OOXML default adj=12500 → innerRatio = 12500/50000 = 0.25
  const a = adj(adjustments, 'adj', 12500) * 2
  return starShape(w, h, 4, Math.min(Math.max(a, 0), 1))
})
presetShapes.set('star5', (w, h, adjustments) => {
  // OOXML: adj=19098, hf=105146, vf=110557 — scaling factors for non-square bounding box
  const aRaw = adjustments?.get('adj') ?? 19098
  const a = Math.min(Math.max(aRaw, 0), 50000)
  const hf = 105146
  const vf = 110557
  const swd2 = ((w / 2) * hf) / 100000
  const shd2 = ((h / 2) * vf) / 100000
  const svc = ((h / 2) * vf) / 100000
  const iwd2 = (swd2 * a) / 50000
  const ihd2 = (shd2 * a) / 50000
  const cx = w / 2
  const step = (2 * Math.PI) / 5
  const halfStep = step / 2
  const startAngle = -Math.PI / 2
  const parts: string[] = []
  for (let i = 0; i < 5; i++) {
    const outerAngle = startAngle + step * i
    const innerAngle = outerAngle + halfStep
    const ox = cx + swd2 * Math.cos(outerAngle)
    const oy = svc + shd2 * Math.sin(outerAngle)
    const ix = cx + iwd2 * Math.cos(innerAngle)
    const iy = svc + ihd2 * Math.sin(innerAngle)
    parts.push(i === 0 ? `M${ox},${oy}` : `L${ox},${oy}`)
    parts.push(`L${ix},${iy}`)
  }
  parts.push('Z')
  return parts.join(' ')
})
presetShapes.set('star6', (w, h, adjustments) => {
  // OOXML: adj=28868, hf=115470 — horizontal scaling factor
  const aRaw = adjustments?.get('adj') ?? 28868
  const a = Math.min(Math.max(aRaw, 0), 50000)
  const hf = 115470
  const swd2 = ((w / 2) * hf) / 100000
  const shd2 = h / 2 // no vf for star6
  const iwd2 = (swd2 * a) / 50000
  const ihd2 = (shd2 * a) / 50000
  const cx = w / 2
  const cy = h / 2
  const step = (2 * Math.PI) / 6
  const halfStep = step / 2
  const startAngle = -Math.PI / 2
  const parts: string[] = []
  for (let i = 0; i < 6; i++) {
    const outerAngle = startAngle + step * i
    const innerAngle = outerAngle + halfStep
    const ox = cx + swd2 * Math.cos(outerAngle)
    const oy = cy + shd2 * Math.sin(outerAngle)
    const ix = cx + iwd2 * Math.cos(innerAngle)
    const iy = cy + ihd2 * Math.sin(innerAngle)
    parts.push(i === 0 ? `M${ox},${oy}` : `L${ox},${oy}`)
    parts.push(`L${ix},${iy}`)
  }
  parts.push('Z')
  return parts.join(' ')
})
presetShapes.set('star7', (w, h, adjustments) => {
  // OOXML star7: adj=34601, hf=102572, vf=105210 — center shifted to svc
  const aRaw = adjustments?.get('adj') ?? 34601
  const a = Math.min(Math.max(aRaw, 0), 50000)
  const swd2 = ((w / 2) * 102572) / 100000
  const shd2 = ((h / 2) * 105210) / 100000
  const svc = shd2 // = vc * vf/100000 so top vertex at svc - shd2 = 0
  const iwd2 = (swd2 * a) / 50000
  const ihd2 = (shd2 * a) / 50000
  const cx = w / 2
  const step = (2 * Math.PI) / 7
  const halfStep = step / 2
  const startAngle = -Math.PI / 2
  const parts: string[] = []
  for (let i = 0; i < 7; i++) {
    const outerAngle = startAngle + step * i
    const innerAngle = outerAngle + halfStep
    const ox = cx + swd2 * Math.cos(outerAngle)
    const oy = svc + shd2 * Math.sin(outerAngle)
    const ix = cx + iwd2 * Math.cos(innerAngle)
    const iy = svc + ihd2 * Math.sin(innerAngle)
    parts.push(i === 0 ? `M${ox},${oy}` : `L${ox},${oy}`)
    parts.push(`L${ix},${iy}`)
  }
  parts.push('Z')
  return parts.join(' ')
})
presetShapes.set('star8', (w, h, adjustments) => {
  // OOXML: iwd2 = wd2 * adj / 50000. adj default=37500 → innerRatio = 37500/50000 = 0.75
  // adj() divides by 100000, so we multiply by 2 to get adj/50000.
  const a = adj(adjustments, 'adj', 37500) * 2
  return starShape(w, h, 8, Math.min(Math.max(a, 0), 1))
})
presetShapes.set('star10', (w, h, adjustments) => {
  // OOXML: adj=42533, hf=105146 — horizontal scaling factor
  const aRaw = adjustments?.get('adj') ?? 42533
  const a = Math.min(Math.max(aRaw, 0), 50000)
  const hf = 105146
  const swd2 = ((w / 2) * hf) / 100000
  const shd2 = h / 2 // no vf for star10
  const iwd2 = (swd2 * a) / 50000
  const ihd2 = (shd2 * a) / 50000
  const cx = w / 2
  const cy = h / 2
  const step = (2 * Math.PI) / 10
  const halfStep = step / 2
  const startAngle = -Math.PI / 2
  const parts: string[] = []
  for (let i = 0; i < 10; i++) {
    const outerAngle = startAngle + step * i
    const innerAngle = outerAngle + halfStep
    const ox = cx + swd2 * Math.cos(outerAngle)
    const oy = cy + shd2 * Math.sin(outerAngle)
    const ix = cx + iwd2 * Math.cos(innerAngle)
    const iy = cy + ihd2 * Math.sin(innerAngle)
    parts.push(i === 0 ? `M${ox},${oy}` : `L${ox},${oy}`)
    parts.push(`L${ix},${iy}`)
  }
  parts.push('Z')
  return parts.join(' ')
})
presetShapes.set('star12', (w, h, adjustments) => {
  // OOXML default adj=37500 → innerRatio = 0.75
  const a = adj(adjustments, 'adj', 37500) * 2
  return starShape(w, h, 12, Math.min(Math.max(a, 0), 1))
})
presetShapes.set('star16', (w, h, adjustments) => {
  // OOXML default adj=37500 → innerRatio = 0.75
  const a = adj(adjustments, 'adj', 37500) * 2
  return starShape(w, h, 16, Math.min(Math.max(a, 0), 1))
})
presetShapes.set('star24', (w, h, adjustments) => {
  // OOXML default adj=37500 → innerRatio = 0.75
  const a = adj(adjustments, 'adj', 37500) * 2
  return starShape(w, h, 24, Math.min(Math.max(a, 0), 1))
})
presetShapes.set('star32', (w, h, adjustments) => {
  // OOXML default adj=37500 → innerRatio = 0.75
  const a = adj(adjustments, 'adj', 37500) * 2
  return starShape(w, h, 32, Math.min(Math.max(a, 0), 1))
})

// ===== Lines & Connectors =====

// OOXML line: diagonal (0,0→w,h) when both extents are non-zero.
// Keep explicit horizontal/vertical handling for zero-extent cases so 1px SVGs remain visible.
presetShapes.set('line', (w, h) => {
  const safeH = h || 1
  const safeW = w || 1
  if (w === 0) return `M0.5,0 L0.5,${safeH}`
  if (h === 0) return `M0,0.5 L${safeW},0.5`
  return `M0,0 L${w},${h}`
})

// Inverse diagonal line (top-right to bottom-left).
presetShapes.set('lineInv', (w, h) => {
  const safeH = h || 1
  const safeW = w || 1
  if (w === 0) return `M0.5,0 L0.5,${safeH}`
  if (h === 0) return `M0,0.5 L${safeW},0.5`
  return `M${w},0 L0,${h}`
})

// When one dimension is 0, draw horizontal or vertical line (same as 'line') so gradient and stroke are correct
presetShapes.set('straightConnector1', (w, h) => {
  const safeH = h || 1
  const safeW = w || 1
  if (w === 0) return `M0.5,0 L0.5,${safeH}`
  if (h === 0) return `M0,0.5 L${safeW},0.5`
  return `M0,0 L${w},${h}`
})

presetShapes.set('bentConnector2', (w, h) => `M0,0 L${w},0 L${w},${h}`)

presetShapes.set('bentConnector3', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj1', 50000)
  const midX = w * a
  return `M0,0 L${midX},0 L${midX},${h} L${w},${h}`
})

presetShapes.set('bentConnector4', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000)
  const a2 = adj(adjustments, 'adj2', 50000)
  const midX = w * a1
  const midY = h * a2
  return `M0,0 L${midX},0 L${midX},${midY} L${w},${midY} L${w},${h}`
})

presetShapes.set('curvedConnector2', (w, h) => {
  return `M0,0 C${w},0 0,${h} ${w},${h}`
})

presetShapes.set('curvedConnector3', (w, h, adjustments) => {
  // OOXML: two cubic Bezier segments joined at midpoint (x2, vc)
  const x2 = w * adj(adjustments, 'adj1', 50000)
  const x1 = x2 / 2 // +/ l x2 2
  const x3 = (w + x2) / 2 // +/ r x2 2
  const vc = h / 2
  const hd4 = h / 4
  const y3 = (h * 3) / 4
  return `M0,0 C${x1},0 ${x2},${hd4} ${x2},${vc} C${x2},${y3} ${x3},${h} ${w},${h}`
})

presetShapes.set('curvedConnector4', (w, h, adjustments) => {
  // OOXML: three cubic Bezier segments
  const x2 = w * adj(adjustments, 'adj1', 50000)
  const y4 = h * adj(adjustments, 'adj2', 50000)
  const x1 = x2 / 2 // +/ l x2 2
  const x3 = (w + x2) / 2 // +/ r x2 2
  const x4 = (x2 + x3) / 2 // +/ x2 x3 2
  const x5 = (x3 + w) / 2 // +/ x3 r 2
  const y1 = y4 / 2 // +/ t y4 2
  const y2 = y1 / 2 // +/ t y1 2
  const y3 = (y1 + y4) / 2 // +/ y1 y4 2
  const y5 = (h + y4) / 2 // +/ b y4 2
  return [
    `M0,0`,
    `C${x1},0 ${x2},${y2} ${x2},${y1}`,
    `C${x2},${y3} ${x4},${y4} ${x3},${y4}`,
    `C${x5},${y4} ${w},${y5} ${w},${h}`,
  ].join(' ')
})

presetShapes.set('curvedConnector5', (w, h, adjustments) => {
  // OOXML: four cubic Bezier segments
  const x3 = w * adj(adjustments, 'adj1', 50000)
  const y4 = h * adj(adjustments, 'adj2', 50000)
  const x6 = w * adj(adjustments, 'adj3', 50000)
  const x1 = (x3 + x6) / 2 // +/ x3 x6 2
  const x2 = x3 / 2 // +/ l x3 2
  const x4 = (x3 + x1) / 2 // +/ x3 x1 2
  const x5 = (x6 + x1) / 2 // +/ x6 x1 2
  const x7 = (x6 + w) / 2 // +/ x6 r 2
  const y1 = y4 / 2 // +/ t y4 2
  const y2 = y1 / 2 // +/ t y1 2
  const y3 = (y1 + y4) / 2 // +/ y1 y4 2
  const y5 = (h + y4) / 2 // +/ b y4 2
  const y6 = (y5 + y4) / 2 // +/ y5 y4 2
  const y7 = (y5 + h) / 2 // +/ y5 b 2
  return [
    `M0,0`,
    `C${x2},0 ${x3},${y2} ${x3},${y1}`,
    `C${x3},${y3} ${x4},${y4} ${x1},${y4}`,
    `C${x5},${y4} ${x6},${y6} ${x6},${y5}`,
    `C${x6},${y7} ${x7},${h} ${w},${h}`,
  ].join(' ')
})

presetShapes.set('bentConnector5', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000)
  const a2 = adj(adjustments, 'adj2', 50000)
  const a3 = adj(adjustments, 'adj3', 50000)
  const x1 = w * a1
  const y1 = h * a2
  const x2 = w * a3
  return `M0,0 L${x1},0 L${x1},${y1} L${x2},${y1} L${x2},${h} L${w},${h}`
})

// ===== Arrow Shapes =====

presetShapes.set('rightArrow', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000) // shaft width ratio
  const a2 = adj(adjustments, 'adj2', 50000) // head length ratio
  const ss = Math.min(w, h) // OOXML uses short side for head length
  const shaftHalfH = (h * a1) / 2
  const headLen = ss * a2
  const cy = h / 2
  const shaftEnd = w - headLen
  return [
    `M0,${cy - shaftHalfH}`,
    `L${shaftEnd},${cy - shaftHalfH}`,
    `L${shaftEnd},0`,
    `L${w},${cy}`,
    `L${shaftEnd},${h}`,
    `L${shaftEnd},${cy + shaftHalfH}`,
    `L0,${cy + shaftHalfH}`,
    'Z',
  ].join(' ')
})

presetShapes.set('leftArrow', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000)
  const a2 = adj(adjustments, 'adj2', 50000)
  const ss = Math.min(w, h)
  const shaftHalfH = (h * a1) / 2
  const headLen = ss * a2
  const cy = h / 2
  return [
    `M${w},${cy - shaftHalfH}`,
    `L${headLen},${cy - shaftHalfH}`,
    `L${headLen},0`,
    `L0,${cy}`,
    `L${headLen},${h}`,
    `L${headLen},${cy + shaftHalfH}`,
    `L${w},${cy + shaftHalfH}`,
    'Z',
  ].join(' ')
})

presetShapes.set('upArrow', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000)
  const a2 = adj(adjustments, 'adj2', 50000)
  const shaftHalfW = (w * a1) / 2
  const headLen = h * a2
  const cx = w / 2
  return [
    `M${cx - shaftHalfW},${h}`,
    `L${cx - shaftHalfW},${headLen}`,
    `L0,${headLen}`,
    `L${cx},0`,
    `L${w},${headLen}`,
    `L${cx + shaftHalfW},${headLen}`,
    `L${cx + shaftHalfW},${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('downArrow', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000)
  const a2 = adj(adjustments, 'adj2', 50000)
  const shaftHalfW = (w * a1) / 2
  const headLen = h * a2
  const cx = w / 2
  const shaftEnd = h - headLen
  return [
    `M${cx - shaftHalfW},0`,
    `L${cx + shaftHalfW},0`,
    `L${cx + shaftHalfW},${shaftEnd}`,
    `L${w},${shaftEnd}`,
    `L${cx},${h}`,
    `L0,${shaftEnd}`,
    `L${cx - shaftHalfW},${shaftEnd}`,
    'Z',
  ].join(' ')
})

presetShapes.set('downArrowCallout', (w, h, adjustments) => {
  // ECMA-like callout geometry (4 adjustments).
  const adj1 = adjustments?.get('adj1') ?? 25000
  const adj2 = adjustments?.get('adj2') ?? 25000
  const adj3 = adjustments?.get('adj3') ?? 25000
  const adj4 = adjustments?.get('adj4') ?? 64977
  const ss = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adj2, (50000 * w) / Math.max(ss, 1)))
  const a1 = Math.max(0, Math.min(adj1, a2 * 2))
  const a3 = Math.max(0, Math.min(adj3, (100000 * h) / Math.max(ss, 1)))
  const q2 = (a3 * ss) / Math.max(h, 1)
  const a4 = Math.max(0, Math.min(adj4, 100000 - q2))
  const hc = w / 2
  const dx1 = (ss * a2) / 100000
  const dx2 = (ss * a1) / 200000
  const x1 = hc - dx1
  const x2 = hc - dx2
  const x3 = hc + dx2
  const x4 = hc + dx1
  const y3 = h - (ss * a3) / 100000
  const y2 = (h * a4) / 100000
  return [
    `M0,0`,
    `L${w},0`,
    `L${w},${y2}`,
    `L${x3},${y2}`,
    `L${x3},${y3}`,
    `L${x4},${y3}`,
    `L${hc},${h}`,
    `L${x1},${y3}`,
    `L${x2},${y3}`,
    `L${x2},${y2}`,
    `L0,${y2}`,
    'Z',
  ].join(' ')
})

presetShapes.set('rightArrowCallout', (w, h, adjustments) => {
  // OOXML: Rectangle body + right-pointing arrowhead (11-point polygon, 4 adj)
  const ss = Math.min(w, h)
  const maxAdj2 = (50000 * h) / Math.max(ss, 1)
  const a2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, maxAdj2))
  const a1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, a2 * 2))
  const maxAdj3 = (100000 * w) / Math.max(ss, 1)
  const a3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, maxAdj3))
  const q2 = (a3 * ss) / Math.max(w, 1)
  const a4 = Math.max(0, Math.min(adjustments?.get('adj4') ?? 64977, 100000 - q2))
  const vc = h / 2
  const dy1 = (ss * a2) / 100000
  const dy2 = (ss * a1) / 200000
  const y1 = vc - dy1
  const y2 = vc - dy2
  const y3 = vc + dy2
  const y4 = vc + dy1
  const dx3 = (ss * a3) / 100000
  const x3 = w - dx3
  const x2 = (w * a4) / 100000
  return [
    `M0,0`,
    `L${x2},0`,
    `L${x2},${y2}`,
    `L${x3},${y2}`,
    `L${x3},${y1}`,
    `L${w},${vc}`,
    `L${x3},${y4}`,
    `L${x3},${y3}`,
    `L${x2},${y3}`,
    `L${x2},${h}`,
    `L0,${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('leftArrowCallout', (w, h, adjustments) => {
  // OOXML: Mirror of rightArrowCallout — arrowhead points left
  const ss = Math.min(w, h)
  const maxAdj2 = (50000 * h) / Math.max(ss, 1)
  const a2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, maxAdj2))
  const a1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, a2 * 2))
  const maxAdj3 = (100000 * w) / Math.max(ss, 1)
  const a3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, maxAdj3))
  const q2 = (a3 * ss) / Math.max(w, 1)
  const a4 = Math.max(0, Math.min(adjustments?.get('adj4') ?? 64977, 100000 - q2))
  const vc = h / 2
  const dy1 = (ss * a2) / 100000
  const dy2 = (ss * a1) / 200000
  const y1 = vc - dy1
  const y2 = vc - dy2
  const y3 = vc + dy2
  const y4 = vc + dy1
  const x1 = (ss * a3) / 100000
  const dx2 = (w * a4) / 100000
  const x2 = w - dx2
  return [
    `M0,${vc}`,
    `L${x1},${y1}`,
    `L${x1},${y2}`,
    `L${x2},${y2}`,
    `L${x2},0`,
    `L${w},0`,
    `L${w},${h}`,
    `L${x2},${h}`,
    `L${x2},${y3}`,
    `L${x1},${y3}`,
    `L${x1},${y4}`,
    'Z',
  ].join(' ')
})

presetShapes.set('upArrowCallout', (w, h, adjustments) => {
  // OOXML: Vertical variant — arrowhead points up
  const ss = Math.min(w, h)
  const maxAdj2 = (50000 * w) / Math.max(ss, 1)
  const a2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, maxAdj2))
  const a1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, a2 * 2))
  const maxAdj3 = (100000 * h) / Math.max(ss, 1)
  const a3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, maxAdj3))
  const q2 = (a3 * ss) / Math.max(h, 1)
  const a4 = Math.max(0, Math.min(adjustments?.get('adj4') ?? 64977, 100000 - q2))
  const hc = w / 2
  const dx1 = (ss * a2) / 100000
  const dx2 = (ss * a1) / 200000
  const x1 = hc - dx1
  const x2 = hc - dx2
  const x3 = hc + dx2
  const x4 = hc + dx1
  const y1 = (ss * a3) / 100000
  const dy2 = (h * a4) / 100000
  const y2 = h - dy2
  return [
    `M0,${y2}`,
    `L${x2},${y2}`,
    `L${x2},${y1}`,
    `L${x1},${y1}`,
    `L${hc},0`,
    `L${x4},${y1}`,
    `L${x3},${y1}`,
    `L${x3},${y2}`,
    `L${w},${y2}`,
    `L${w},${h}`,
    `L0,${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('upDownArrowCallout', (w, h, adjustments) => {
  // OOXML spec: 4 adjustments
  const adj1Raw = adjustments?.get('adj1') ?? 25000
  const adj2Raw = adjustments?.get('adj2') ?? 25000
  const adj3Raw = adjustments?.get('adj3') ?? 25000
  const adj4Raw = adjustments?.get('adj4') ?? 48123
  const ss = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adj2Raw, (50000 * w) / Math.max(ss, 1)))
  const a1 = Math.max(0, Math.min(adj1Raw, a2 * 2))
  const a3 = Math.max(0, Math.min(adj3Raw, (50000 * h) / Math.max(ss, 1)))
  const q2 = (a3 * ss) / Math.max(h, 1)
  const a4 = Math.max(0, Math.min(adj4Raw, 100000 - q2 - q2))
  const dx1 = (ss * a2) / 100000
  const dx2 = (ss * a1) / 200000
  const hc = w / 2
  const x1 = hc - dx1
  const x2 = hc - dx2
  const x3 = hc + dx2
  const x4 = hc + dx1
  const y1 = (ss * a3) / 100000
  const dy2 = (h * a4) / 200000
  const y2 = h / 2 - dy2
  const y3 = h / 2 + dy2
  const y4 = h - y1
  return [
    `M${hc},0`,
    `L${x4},${y1}`,
    `L${x3},${y1}`,
    `L${x3},${y2}`,
    `L${w},${y2}`,
    `L${w},${y3}`,
    `L${x3},${y3}`,
    `L${x3},${y4}`,
    `L${x4},${y4}`,
    `L${hc},${h}`,
    `L${x1},${y4}`,
    `L${x2},${y4}`,
    `L${x2},${y3}`,
    `L0,${y3}`,
    `L0,${y2}`,
    `L${x2},${y2}`,
    `L${x2},${y1}`,
    `L${x1},${y1}`,
    'Z',
  ].join(' ')
})

presetShapes.set('leftRightArrowCallout', (w, h, adjustments) => {
  // OOXML spec: 4 adjustments
  const adj1Raw = adjustments?.get('adj1') ?? 25000
  const adj2Raw = adjustments?.get('adj2') ?? 25000
  const adj3Raw = adjustments?.get('adj3') ?? 25000
  const adj4Raw = adjustments?.get('adj4') ?? 48123
  const ss = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adj2Raw, (50000 * h) / Math.max(ss, 1)))
  const a1 = Math.max(0, Math.min(adj1Raw, a2 * 2))
  const a3 = Math.max(0, Math.min(adj3Raw, (50000 * w) / Math.max(ss, 1)))
  const q2 = (a3 * ss) / Math.max(w, 1)
  const a4 = Math.max(0, Math.min(adj4Raw, 100000 - q2 - q2))
  const dy1 = (ss * a2) / 100000
  const dy2 = (ss * a1) / 200000
  const vc = h / 2
  const y1 = vc - dy1
  const y2 = vc - dy2
  const y3 = vc + dy2
  const y4 = vc + dy1
  const x1 = (ss * a3) / 100000
  const dx2 = (w * a4) / 200000
  const x2 = w / 2 - dx2
  const x3 = w / 2 + dx2
  const x4 = w - x1
  return [
    `M0,${vc}`,
    `L${x1},${y1}`,
    `L${x1},${y2}`,
    `L${x2},${y2}`,
    `L${x2},0`,
    `L${x3},0`,
    `L${x3},${y2}`,
    `L${x4},${y2}`,
    `L${x4},${y1}`,
    `L${w},${vc}`,
    `L${x4},${y4}`,
    `L${x4},${y3}`,
    `L${x3},${y3}`,
    `L${x3},${h}`,
    `L${x2},${h}`,
    `L${x2},${y3}`,
    `L${x1},${y3}`,
    `L${x1},${y4}`,
    'Z',
  ].join(' ')
})

presetShapes.set('uturnArrow', (w, h, adjustments) => {
  // ECMA-like U-turn arrow geometry (5 adjustments).
  const adj1 = adjustments?.get('adj1') ?? 25000
  const adj2 = adjustments?.get('adj2') ?? 25000
  const adj3 = adjustments?.get('adj3') ?? 25000
  const adj4 = adjustments?.get('adj4') ?? 43750
  const adj5 = adjustments?.get('adj5') ?? 75000
  const ss = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adj2, 25000))
  const a1 = Math.max(0, Math.min(adj1, a2 * 2))
  const q2 = (a1 * ss) / Math.max(h, 1)
  const q3 = 100000 - q2
  const a3 = Math.max(0, Math.min(adj3, (q3 * h) / Math.max(ss, 1)))
  const minAdj5 = ((a3 + a1) * ss) / Math.max(h, 1)
  const a5 = Math.max(minAdj5, Math.min(adj5, 100000))

  const th = (ss * a1) / 100000
  const aw2 = (ss * a2) / 100000
  const th2 = th / 2
  const dh2 = aw2 - th2
  const y5 = (h * a5) / 100000
  const ah = (ss * a3) / 100000
  const y4 = y5 - ah
  const x9 = w - dh2
  const bs = Math.min(x9 / 2, y4)
  const a4 = Math.max(0, Math.min(adj4, (100000 * bs) / Math.max(ss, 1)))
  const bd = (ss * a4) / 100000
  const bd2 = Math.max(bd - th, 0)
  const x3 = th + bd2
  const x8 = w - aw2
  const x6 = x8 - aw2
  const x7 = x6 + dh2
  const x4 = x9 - bd
  const x5 = x7 - bd2

  return [
    `M0,${h}`,
    `L0,${bd}`,
    bd > 0.1 ? `A${bd},${bd} 0 0,1 ${bd},0` : `L0,0`,
    `L${x4},0`,
    bd > 0.1 ? `A${bd},${bd} 0 0,1 ${x9},${bd}` : `L${x9},0`,
    `L${x9},${y4}`,
    `L${w},${y4}`,
    `L${x8},${y5}`,
    `L${x6},${y4}`,
    `L${x7},${y4}`,
    `L${x7},${x3}`,
    bd2 > 0.1 ? `A${bd2},${bd2} 0 0,0 ${x5},${th}` : `L${x5},${th}`,
    `L${x3},${th}`,
    bd2 > 0.1 ? `A${bd2},${bd2} 0 0,0 ${th},${x3}` : `L${th},${x3}`,
    `L${th},${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('leftRightArrow', (w, h, adjustments) => {
  // OOXML: adj1=50000 (shaft width), adj2=50000 (head length based on ss)
  const ss = Math.min(w, h)
  const hd2 = h / 2
  const maxAdj2 = ss > 0 ? (50000 * w) / ss : 0
  const a1 = Math.min(Math.max(adjustments?.get('adj1') ?? 50000, 0), 100000)
  const a2 = Math.min(Math.max(adjustments?.get('adj2') ?? 50000, 0), maxAdj2)
  const x2 = (ss * a2) / 100000
  const x3 = w - x2
  const dy = (h * a1) / 200000
  const vc = hd2
  const y1 = vc - dy
  const y2 = vc + dy
  const dx1 = hd2 > 0 ? (y1 * x2) / hd2 : 0
  const _x1 = x2 - dx1
  const _x4 = x3 + dx1
  return [
    `M0,${vc}`,
    `L${x2},0`,
    `L${x2},${y1}`,
    `L${x3},${y1}`,
    `L${x3},0`,
    `L${w},${vc}`,
    `L${x3},${h}`,
    `L${x3},${y2}`,
    `L${x2},${y2}`,
    `L${x2},${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('leftUpArrow', (w, h, adjustments) => {
  // OOXML preset formula (presetShapeDefinitions.xml -> leftUpArrow)
  const rawAdj2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, 50000))
  const maxAdj1 = rawAdj2 * 2
  const rawAdj1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, maxAdj1))
  const maxAdj3 = 100000 - maxAdj1
  const rawAdj3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, maxAdj3))

  const ss = Math.min(w, h)
  const x1 = (ss * rawAdj3) / 100000
  const dx2 = (ss * rawAdj2) / 50000
  const x2 = w - dx2
  const y2 = h - dx2
  const dx4 = (ss * rawAdj2) / 100000
  const x4 = w - dx4
  const y4 = h - dx4
  const dx3 = (ss * rawAdj1) / 200000
  const x3 = x4 - dx3
  const x5 = x4 + dx3
  const y3 = y4 - dx3
  const y5 = y4 + dx3

  return [
    `M0,${y4}`,
    `L${x1},${y2}`,
    `L${x1},${y3}`,
    `L${x3},${y3}`,
    `L${x3},${x1}`,
    `L${x2},${x1}`,
    `L${x4},0`,
    `L${w},${x1}`,
    `L${x5},${x1}`,
    `L${x5},${y5}`,
    `L${x1},${y5}`,
    `L${x1},${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('upDownArrow', (w, h, adjustments) => {
  // OOXML spec: adj1=50000 (shaft width), adj2=50000 (head length on ss)
  const adj1Raw = adjustments?.get('adj1') ?? 50000
  const adj2Raw = adjustments?.get('adj2') ?? 50000
  const ss = Math.min(w, h)
  const maxAdj2 = (50000 * h) / Math.max(ss, 1)
  const a2 = Math.max(0, Math.min(adj2Raw, maxAdj2))
  const a1 = Math.max(0, Math.min(adj1Raw, 100000))
  const dx1 = (ss * a1) / 200000 // shaft half-width
  const dy = (ss * a2) / 100000 // arrowhead length
  const hc = w / 2
  return [
    `M${hc},0`,
    `L${w},${dy}`,
    `L${hc + dx1},${dy}`,
    `L${hc + dx1},${h - dy}`,
    `L${w},${h - dy}`,
    `L${hc},${h}`,
    `L0,${h - dy}`,
    `L${hc - dx1},${h - dy}`,
    `L${hc - dx1},${dy}`,
    `L0,${dy}`,
    'Z',
  ].join(' ')
})

presetShapes.set('notchedRightArrow', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 50000) // shaft width ratio
  const a2 = adj(adjustments, 'adj2', 50000) // head length ratio
  const ss = Math.min(w, h) // OOXML uses short side for head length
  const shaftHalfH = (h * a1) / 2
  const headLen = ss * a2
  const cy = h / 2
  const shaftEnd = w - headLen
  // Notch depth: OOXML formula dxn = dy1 * dx2 / hd2 = shaftHalfH * headLen / (h/2)
  const notchDepth = cy > 0 ? (shaftHalfH * headLen) / cy : 0
  return [
    `M0,${cy - shaftHalfH}`,
    `L${shaftEnd},${cy - shaftHalfH}`,
    `L${shaftEnd},0`,
    `L${w},${cy}`,
    `L${shaftEnd},${h}`,
    `L${shaftEnd},${cy + shaftHalfH}`,
    `L0,${cy + shaftHalfH}`,
    `L${notchDepth},${cy}`,
    'Z',
  ].join(' ')
})

presetShapes.set('chevron', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 50000)
  const ss = Math.min(w, h)
  const offset = ss * a
  return [
    `M0,0`,
    `L${w - offset},0`,
    `L${w},${h / 2}`,
    `L${w - offset},${h}`,
    `L0,${h}`,
    `L${offset},${h / 2}`,
    'Z',
  ].join(' ')
})

presetShapes.set('homePlate', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 50000)
  const ss = Math.min(w, h)
  const offset = ss * a
  const shoulderX = w - offset
  return [`M0,0`, `L${shoulderX},0`, `L${w},${h / 2}`, `L${shoulderX},${h}`, `L0,${h}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('stripedRightArrow', (w, h, adjustments) => {
  // OOXML: adj1=50000, adj2=50000 (max 84375). Stripes at ssd32, ssd16-ssd8, x4=ss*5/32.
  const ss = Math.min(w, h)
  const maxAdj2 = ss > 0 ? (84375 * w) / ss : 84375
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj1', 50000), 0), 100000)
  const a2 = Math.min(Math.max(adjRaw(adjustments, 'adj2', 50000), 0), maxAdj2)
  const dy1 = (h * a1) / 200000
  const dx5 = (ss * a2) / 100000
  const x5 = w - dx5
  const vc = h / 2
  const y1 = vc - dy1
  const y2 = vc + dy1
  const ssd32 = ss / 32
  const ssd16 = ss / 16
  const ssd8 = ss / 8
  const x4 = (ss * 5) / 32
  return [
    // Stripe 1: 0 to ssd32
    `M0,${y1} L${ssd32},${y1} L${ssd32},${y2} L0,${y2} Z`,
    // Stripe 2: ssd16 to ssd8
    `M${ssd16},${y1} L${ssd8},${y1} L${ssd8},${y2} L${ssd16},${y2} Z`,
    // Main body + arrowhead: x4 to r
    `M${x4},${y1}`,
    `L${x5},${y1}`,
    `L${x5},0`,
    `L${w},${vc}`,
    `L${x5},${h}`,
    `L${x5},${y2}`,
    `L${x4},${y2}`,
    'Z',
  ].join(' ')
})

// ===== Bent / Curved / Special Arrows =====

presetShapes.set('bentArrow', (w, h, adjustments) => {
  // OOXML bentArrow: L-shaped arrow with rounded bend, arrowhead pointing right.
  // Uses 4 adjustments per ECMA-376 spec.
  const ss = Math.min(w, h)

  // Constrained adjustments (raw values, not fractions — we do our own math)
  const adj2Raw = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, 50000))
  const maxAdj1 = adj2Raw * 2
  const adj1Raw = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, maxAdj1))
  const adj3Raw = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, 50000))

  const th = (ss * adj1Raw) / 100000 // shaft width
  const aw2 = (ss * adj2Raw) / 100000 // arrowhead half-width
  const th2 = th / 2
  const dh2 = aw2 - th2 // arrowhead extension beyond shaft
  const ah = (ss * adj3Raw) / 100000 // arrowhead length

  const bw = w - ah
  const bh = h - dh2
  const bs = Math.min(bw, bh)
  const maxAdj4 = bs > 0 ? (100000 * bs) / ss : 0
  const adj4Raw = Math.max(0, Math.min(adjustments?.get('adj4') ?? 43750, maxAdj4))
  const bd = (ss * adj4Raw) / 100000 // outer bend radius

  const bd2 = Math.max(bd - th, 0) // inner bend radius
  const x3 = th + bd2
  const x4 = w - ah

  const y3 = dh2 + th
  const y4 = y3 + dh2
  const y5 = dh2 + bd

  // OOXML arcTo: from current point, arc with radii (wR, hR), start angle stAng, sweep swAng.
  // Arc 1: outer bend — from (0, y5), radii=bd, 180°→270° (sweep +90°)
  //   Center of arc is at (bd, y5) relative, endpoint at (bd, y5-bd) = (bd, dh2)
  //   SVG: A bd,bd 0 0,1 bd,dh2
  // Arc 2: inner bend — from (x3, y3), radii=bd2, 270°→180° (sweep -90°)
  //   Center at (x3, y3+bd2), endpoint at (x3-bd2, y3+bd2) = (th, y3+bd2)
  //   SVG: A bd2,bd2 0 0,0 th,y6  where y6 = y3+bd2

  const y6 = y3 + bd2

  const parts: string[] = [
    `M0,${h}`, // bottom-left
    `L0,${y5}`, // up left edge to arc start
  ]

  // Outer arc (rounded bend, going from left edge up to top edge)
  if (bd > 0.1) {
    parts.push(`A${bd},${bd} 0 0,1 ${bd},${dh2}`)
  } else {
    parts.push(`L0,${dh2}`) // degenerate: straight corner
  }

  parts.push(
    `L${x4},${dh2}`, // horizontal to arrowhead base (top)
    `L${x4},0`, // up to arrowhead top-left wing
    `L${w},${aw2}`, // arrowhead tip (pointing right)
    `L${x4},${y4}`, // arrowhead bottom wing
    `L${x4},${y3}`, // back to arrowhead base (bottom)
    `L${x3},${y3}` // horizontal back toward bend
  )

  // Inner arc (rounded bend, going from top down to right side of shaft)
  if (bd2 > 0.1) {
    parts.push(`A${bd2},${bd2} 0 0,0 ${th},${y6}`)
  } else {
    parts.push(`L${th},${y3}`) // degenerate: straight corner
  }

  parts.push(
    `L${th},${h}`, // down right side of shaft to bottom
    'Z'
  )

  return parts.join(' ')
})

presetShapes.set('bentUpArrow', (w, h, adjustments) => {
  // OOXML preset formula (presetShapeDefinitions.xml -> bentUpArrow):
  // x/y variables are solved from adj1/2/3 in [0..50000], ss=min(w,h).
  const raw1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, 50000))
  const raw2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, 50000))
  const raw3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, 50000))
  const ss = Math.min(w, h)

  const y1 = (ss * raw3) / 100000
  const dx1 = (ss * raw2) / 50000
  const x1 = w - dx1
  const dx3 = (ss * raw2) / 100000
  const x3 = w - dx3
  const dx2 = (ss * raw1) / 200000
  const x2 = x3 - dx2
  const x4 = x3 + dx2
  const dy2 = (ss * raw1) / 100000
  const y2 = h - dy2

  return [
    `M0,${y2}`,
    `L${x2},${y2}`,
    `L${x2},${y1}`,
    `L${x1},${y1}`,
    `L${x3},0`,
    `L${w},${y1}`,
    `L${x4},${y1}`,
    `L${x4},${h}`,
    `L0,${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('curvedRightArrow', (w, h, adjustments) => {
  // Keep geometry aligned with OOXML preset math. Use local arc helper here
  // because preset formulas mix positive/negative sweeps that do not map 1:1
  // to the generic shapeArc() helper used in other shapes.
  const adj1Raw = adjustments?.get('adj1') ?? 25000
  const adj2Raw = adjustments?.get('adj2') ?? 50000
  const adj3Raw = adjustments?.get('adj3') ?? 25000

  const cnstVal1 = 50000
  const cnstVal2 = 100000

  const hd2 = h / 2
  const r = w
  const b = h
  const l = 0
  const c3d4 = 270
  const cd2 = 180
  const cd4 = 90
  const ss = Math.max(Math.min(w, h), 1)

  const maxAdj2 = (cnstVal1 * h) / ss
  const a2 = Math.max(0, Math.min(adj2Raw, maxAdj2))
  const a1 = Math.max(0, Math.min(adj1Raw, a2))
  const th = (ss * a1) / cnstVal2
  const aw = (ss * a2) / cnstVal2
  const q1 = (th + aw) / 4
  const hR = hd2 - q1
  const q7 = hR * 2
  const q8 = q7 * q7
  const q9 = th * th
  const q10 = Math.max(q8 - q9, 0)
  const q11 = Math.sqrt(q10)
  const iDx = (q11 * w) / Math.max(q7, 1e-6)
  const maxAdj3 = (cnstVal2 * iDx) / ss
  const a3 = Math.max(0, Math.min(adj3Raw, maxAdj3))
  const ah = (ss * a3) / cnstVal2
  const y3 = hR + th
  const q2 = w * w
  const q3 = ah * ah
  const q4 = Math.max(q2 - q3, 0)
  const q5 = Math.sqrt(q4)
  const dy = (q5 * hR) / Math.max(w, 1e-6)
  const y5 = hR + dy
  const y7 = y3 + dy
  const q6 = aw - th
  const dh = q6 / 2
  const y4 = y5 - dh
  const y8 = y7 + dh
  const aw2 = aw / 2
  const y6 = b - aw2
  const x1 = r - ah
  const swAng = Math.atan(dy / Math.max(ah, 1e-6))
  const stAng = Math.PI - swAng
  const mswAng = -swAng
  const q12 = th / 2
  const dang2 = Math.atan2(q12, Math.max(iDx, 1e-6))
  const swAng2 = dang2 - Math.PI / 2
  const stAngDg = (stAng * 180) / Math.PI
  const mswAngDg = (mswAng * 180) / Math.PI
  const swAngDg = (swAng * 180) / Math.PI
  const swAng2Dg = (swAng2 * 180) / Math.PI

  const arc = (
    cx: number,
    cy: number,
    rx: number,
    ry: number,
    startDeg: number,
    endDeg: number
  ): string => {
    const s = (startDeg * Math.PI) / 180
    const e = (endDeg * Math.PI) / 180
    const xS = cx + rx * Math.cos(s)
    const yS = cy + ry * Math.sin(s)
    const xE = cx + rx * Math.cos(e)
    const yE = cy + ry * Math.sin(e)
    const delta = endDeg - startDeg
    const largeArc = Math.abs(delta) > 180 ? 1 : 0
    const sweep = delta >= 0 ? 1 : 0
    return `M${xS},${yS} A${rx},${ry} 0 ${largeArc},${sweep} ${xE},${yE}`
  }

  return [
    `M${l},${hR}`,
    arc(w, hR, w, hR, cd2, cd2 + mswAngDg).replace('M', 'L'),
    `L${x1},${y5}`,
    `L${x1},${y4}`,
    `L${r},${y6}`,
    `L${x1},${y8}`,
    `L${x1},${y7}`,
    arc(w, y3, w, hR, stAngDg, stAngDg + swAngDg).replace('M', 'L'),
    'Z',
    arc(w, hR, w, hR, cd2, cd2 + cd4),
    `L${r},${th}`,
    arc(w, y3, w, hR, c3d4, c3d4 + swAng2Dg).replace('M', 'L'),
    'Z',
  ].join(' ')
})

presetShapes.set('curvedLeftArrow', (w, h, adjustments) =>
  mirrorAbsolutePathHorizontally(presetShapes.get('curvedRightArrow')!(w, h, adjustments), w)
)

function splitFirstClosedContour(path: string): { outer: string; remainder: string } {
  const closeIdx = path.indexOf('Z')
  if (closeIdx === -1) {
    return { outer: path, remainder: '' }
  }
  const outer = path.slice(0, closeIdx + 1).trim()
  const remainder = path.slice(closeIdx + 1).trim()
  return { outer, remainder }
}

function buildCurvedArrowMultiPath(
  shapeName: 'curvedRightArrow' | 'curvedLeftArrow',
  w: number,
  h: number,
  adjustments?: Map<string, number>
): PresetSubPath[] {
  const fullPath = presetShapes.get(shapeName)!(w, h, adjustments)
  const { outer, remainder } = splitFirstClosedContour(fullPath)
  if (!remainder) {
    return [{ d: fullPath, fill: 'norm', stroke: true }]
  }

  if (shapeName === 'curvedRightArrow') {
    return [
      { d: remainder, fill: 'norm', stroke: true },
      { d: outer, fill: 'norm', stroke: true },
    ]
  }

  return [
    { d: outer, fill: 'norm', stroke: true },
    { d: remainder, fill: 'norm', stroke: true },
  ]
}

function buildCurvedVerticalArrowMultiPath(
  shapeName: 'curvedUpArrow' | 'curvedDownArrow',
  w: number,
  h: number,
  adjustments?: Map<string, number>
): PresetSubPath[] {
  const downFullPath = presetShapes.get('curvedDownArrow')!(w, h, adjustments)
  const { outer, remainder } = splitFirstClosedContour(downFullPath)
  const ordered: PresetSubPath[] = remainder
    ? [
        { d: remainder, fill: 'norm', stroke: true },
        { d: outer, fill: 'norm', stroke: true },
      ]
    : [{ d: downFullPath, fill: 'norm', stroke: true }]

  if (shapeName === 'curvedDownArrow') {
    return ordered
  }

  const mirrored: PresetSubPath[] = ordered.map((path) => ({
    ...path,
    d: mirrorAbsolutePathVertically(path.d, h),
  }))
  return mirrored.reverse()
}

/**
 * Convert OOXML arcTo to SVG arc endpoint and command string.
 * OOXML arcTo: wR, hR (radii), stAng, swAng (degrees).
 * Current point is at stAng on the arc ellipse.
 * Returns { path, endX, endY }.
 */
presetShapes.set('curvedUpArrow', (w, h, adjustments) => {
  const arc = (
    cx: number,
    cy: number,
    rx: number,
    ry: number,
    startDeg: number,
    endDeg: number
  ): string => {
    const s = (startDeg * Math.PI) / 180
    const e = (endDeg * Math.PI) / 180
    const xS = cx + rx * Math.cos(s)
    const yS = cy + ry * Math.sin(s)
    const xE = cx + rx * Math.cos(e)
    const yE = cy + ry * Math.sin(e)
    const delta = endDeg - startDeg
    const largeArc = Math.abs(delta) > 180 ? 1 : 0
    const sweep = delta >= 0 ? 1 : 0
    return `M${xS},${yS} A${rx},${ry} 0 ${largeArc},${sweep} ${xE},${yE}`
  }

  const ss = Math.min(w, h)
  const wd2 = w / 2
  const a1Raw = adjustments?.get('adj1') ?? 25000
  const a2Raw = adjustments?.get('adj2') ?? 50000
  const a3Raw = adjustments?.get('adj3') ?? 25000
  const maxAdj2 = (50000 * w) / Math.max(ss, 1)
  const a2 = Math.max(0, Math.min(a2Raw, maxAdj2))
  const a1 = Math.max(0, Math.min(a1Raw, 100000))
  const th = (ss * a1) / 100000
  const aw = (ss * a2) / 100000
  const q1 = (th + aw) / 4
  const wR = wd2 - q1
  const q7 = wR * 2
  const idy = (Math.sqrt(Math.max(q7 * q7 - th * th, 0)) * h) / Math.max(q7, 1)
  const maxAdj3 = (100000 * idy) / Math.max(ss, 1)
  const a3 = Math.max(0, Math.min(a3Raw, maxAdj3))
  const ah = (ss * a3) / 100000
  const x3 = wR + th
  const dx = (Math.sqrt(Math.max(h * h - ah * ah, 0)) * wR) / Math.max(h, 1)
  const x5 = wR + dx
  const x7 = x3 + dx
  const dh = (aw - th) / 2
  const x4 = x5 - dh
  const x8 = x7 + dh
  const x6 = w - aw / 2
  const y1 = ah

  const swAng = Math.atan2(dx, ah)
  const dang2 = Math.atan2(th / 2, idy)
  const stAng2 = Math.PI / 2 - dang2
  const swAng2 = dang2 - swAng
  const stAng3 = Math.PI / 2 - swAng
  const stAng2Deg = (stAng2 * 180) / Math.PI
  const swAng2Deg = (swAng2 * 180) / Math.PI
  const stAng3Deg = (stAng3 * 180) / Math.PI
  const swAngDeg = (swAng * 180) / Math.PI

  return [
    arc(wR, 0, wR, h, stAng2Deg, stAng2Deg + swAng2Deg),
    `L${x5},${y1}`,
    `L${x4},${y1}`,
    `L${x6},0`,
    `L${x8},${y1}`,
    `L${x7},${y1}`,
    arc(x3, 0, wR, h, stAng3Deg, stAng3Deg + swAngDeg).replace('M', 'L'),
    `L${wR},${h}`,
    arc(wR, 0, wR, h, 90, 180).replace('M', 'L'),
    `L${th},0`,
    arc(x3, 0, wR, h, 180, 90).replace('M', 'L'),
    'Z',
  ].join(' ')
})

presetShapes.set('curvedDownArrow', (w, h, adjustments) => {
  const arc = (
    cx: number,
    cy: number,
    rx: number,
    ry: number,
    startDeg: number,
    endDeg: number
  ): string => {
    const s = (startDeg * Math.PI) / 180
    const e = (endDeg * Math.PI) / 180
    const xS = cx + rx * Math.cos(s)
    const yS = cy + ry * Math.sin(s)
    const xE = cx + rx * Math.cos(e)
    const yE = cy + ry * Math.sin(e)
    const delta = endDeg - startDeg
    const largeArc = Math.abs(delta) > 180 ? 1 : 0
    const sweep = delta >= 0 ? 1 : 0
    return `M${xS},${yS} A${rx},${ry} 0 ${largeArc},${sweep} ${xE},${yE}`
  }

  const ss = Math.min(w, h)
  const wd2 = w / 2
  const a1Raw = adjustments?.get('adj1') ?? 25000
  const a2Raw = adjustments?.get('adj2') ?? 50000
  const a3Raw = adjustments?.get('adj3') ?? 25000
  const maxAdj2 = (50000 * w) / Math.max(ss, 1)
  const a2 = Math.max(0, Math.min(a2Raw, maxAdj2))
  const a1 = Math.max(0, Math.min(a1Raw, 100000))
  const th = (ss * a1) / 100000
  const aw = (ss * a2) / 100000
  const q1 = (th + aw) / 4
  const wR = wd2 - q1
  const q7 = wR * 2
  const idy = (Math.sqrt(Math.max(q7 * q7 - th * th, 0)) * h) / Math.max(q7, 1)
  const maxAdj3 = (100000 * idy) / Math.max(ss, 1)
  const a3 = Math.max(0, Math.min(a3Raw, maxAdj3))
  const ah = (ss * a3) / 100000
  const x3 = wR + th
  const dx = (Math.sqrt(Math.max(h * h - ah * ah, 0)) * wR) / Math.max(h, 1)
  const x5 = wR + dx
  const x7 = x3 + dx
  const dh = (aw - th) / 2
  const x4 = x5 - dh
  const x8 = x7 + dh
  const x6 = w - aw / 2
  const y1 = h - ah

  const swAng = Math.atan2(dx, ah)
  const swAngDeg = (swAng * 180) / Math.PI
  const dang2 = Math.atan2(th / 2, idy)
  const dang2Deg = (dang2 * 180) / Math.PI
  const stAng = 270 + swAngDeg
  const stAng2 = 270 - dang2Deg
  const swAng2 = dang2Deg - 90
  const swAng3 = 90 + dang2Deg

  return [
    `M${x6},${h}`,
    `L${x4},${y1}`,
    `L${x5},${y1}`,
    arc(wR, h, wR, h, stAng, stAng - swAngDeg).replace('M', 'L'),
    `L${x3},0`,
    arc(x3, h, wR, h, 270, 270 + swAngDeg).replace('M', 'L'),
    `L${x5 + th},${y1}`,
    `L${x8},${y1}`,
    'Z',
    `M${x3},0`,
    arc(x3, h, wR, h, stAng2, stAng2 + swAng2).replace('M', 'L'),
    arc(wR, h, wR, h, 180, 180 + swAng3).replace('M', 'L'),
    'Z',
  ].join(' ')
})

function buildCircularArrowPath(
  w: number,
  h: number,
  adjustments?: Map<string, number>,
  _mirrorX = false,
  variant: 'circularArrow' | 'leftCircularArrow' = 'circularArrow'
): string {
  // OOXML circularArrow / leftCircularArrow: same guide formulas, different default adjustments.
  const hc = w / 2
  const vc = h / 2
  const wd2 = w / 2
  const hd2 = h / 2
  const ss = Math.min(w, h)
  const cd2 = 10800000 // 180° in 60000ths

  const toRad60k = (a: number) => ((a / 60000) * Math.PI) / 180

  // OOXML formula helpers
  const ooxSin = (val: number, ang: number) => val * Math.sin(toRad60k(ang))
  const ooxCos = (val: number, ang: number) => val * Math.cos(toRad60k(ang))
  const cat2 = (r: number, ht: number, wt: number) => r * Math.cos(Math.atan2(wt, ht))
  const sat2 = (r: number, ht: number, wt: number) => r * Math.sin(Math.atan2(wt, ht))
  // OOXML: at2(x, y) = atan2(y, x) — first arg is x, second is y
  const at2 = (x: number, y: number) => ((Math.atan2(y, x) * 180) / Math.PI) * 60000
  const modF = (x: number, y: number, z: number) => Math.sqrt(x * x + y * y + z * z)

  // Adjustments — leftCircularArrow has different OOXML defaults
  const isLeft = variant === 'leftCircularArrow'
  const adj1 = adjustments?.get('adj1') ?? 12500
  const adj2 = adjustments?.get('adj2') ?? (isLeft ? -1142319 : 1142319)
  const adj3 = adjustments?.get('adj3') ?? (isLeft ? 1142319 : 20457681)
  const adj4 = adjustments?.get('adj4') ?? 10800000
  const adj5v = adjustments?.get('adj5') ?? 12500

  const a5 = Math.max(0, Math.min(adj5v, 25000))
  const maxAdj1 = a5 * 2
  const a1 = Math.max(0, Math.min(adj1, maxAdj1))
  const enAng = Math.max(1, Math.min(adj3, 21599999))
  const stAng = Math.max(0, Math.min(adj4, 21599999))

  const th = (ss * a1) / 100000
  const thh = (ss * a5) / 100000
  const th2 = th / 2

  const rw1 = wd2 + th2 - thh
  const rh1 = hd2 + th2 - thh
  const rw2 = rw1 - th
  const rh2 = rh1 - th
  const rw3 = rw2 + th2
  const rh3 = rh2 + th2

  // Point H (mid-radius at end angle)
  const wtH = ooxSin(rw3, enAng)
  const htH = ooxCos(rh3, enAng)
  const dxH = cat2(rw3, htH, wtH)
  const dyH = sat2(rh3, htH, wtH)
  const xH = hc + dxH
  const yH = vc + dyH

  // Compute max arrowhead angle
  const rI = Math.min(rw2, rh2)
  const u1 = dxH * dxH
  const u2 = dyH * dyH
  const u3 = rI * rI
  const u4 = u1 - u3
  const u5 = u2 - u3
  const u6 = u2 !== 0 ? (u4 * u5) / u1 : 0
  const u7 = u2 !== 0 ? u6 / u2 : 0
  const u8 = 1 - u7
  const u9 = Math.sqrt(Math.max(0, u8))
  const u10 = dxH !== 0 ? u4 / dxH : 0
  const u11 = dyH !== 0 ? u10 / dyH : 0
  const u12 = u11 !== 0 ? (1 + u9) / u11 : 0
  const u13 = at2(1, u12)
  const u14 = u13 + 21600000
  const u15 = u13 >= 0 ? u13 : u14
  const u16 = u15 - enAng
  const u17 = u16 + 21600000
  const u18 = u16 >= 0 ? u16 : u17
  const u19 = u18 - cd2
  const u20 = u18 - 21600000
  const u21 = u19 >= 0 ? u20 : u18
  const maxAng = Math.abs(u21)
  let aAng: number
  if (isLeft) {
    // leftCircularArrow: minAng = -abs(u21), a2 = -abs(adj2), aAng = pin(minAng, a2, 0)
    const minAng = -maxAng
    const a2 = -Math.abs(adj2)
    aAng = Math.max(minAng, Math.min(a2, 0))
  } else {
    aAng = Math.max(0, Math.min(adj2, maxAng))
  }
  const ptAng = enAng + aAng

  // Point A (arrowhead tip)
  const wtA = ooxSin(rw3, ptAng)
  const htA = ooxCos(rh3, ptAng)
  const dxA = cat2(rw3, htA, wtA)
  const dyA = sat2(rh3, htA, wtA)
  const xA = hc + dxA
  const yA = vc + dyA

  // Point E (outer arc start)
  const wtE = ooxSin(rw1, stAng)
  const htE = ooxCos(rh1, stAng)
  const dxE = cat2(rw1, htE, wtE)
  const dyE = sat2(rh1, htE, wtE)
  const xE = hc + dxE
  const yE = vc + dyE

  // Points G and B (arrowhead base, offset from H by thh at angle ptAng)
  const dxG = ooxCos(thh, ptAng)
  const dyG = ooxSin(thh, ptAng)
  const xG = xH + dxG
  const yG = yH + dyG
  const xB = xH - dxG
  const yB = yH - dyG

  // Scale to normalized circle for line-circle intersection
  const sx1 = xB - hc
  const sy1 = yB - vc
  const sx2 = xG - hc
  const sy2 = yG - vc

  // Outer circle intersection
  const rO = Math.min(rw1, rh1)
  const x1O = rw1 !== 0 ? (sx1 * rO) / rw1 : 0
  const y1O = rh1 !== 0 ? (sy1 * rO) / rh1 : 0
  const x2O = rw1 !== 0 ? (sx2 * rO) / rw1 : 0
  const y2O = rh1 !== 0 ? (sy2 * rO) / rh1 : 0

  const dxO = x2O - x1O
  const dyO = y2O - y1O
  const dOval = modF(dxO, dyO, 0)

  const q1 = x1O * y2O
  const q2 = x2O * y1O
  const DO = q1 - q2

  const q3 = rO * rO
  const q4 = dOval * dOval
  const q5 = q3 * q4
  const q6 = DO * DO
  const q7 = q5 - q6
  const q8 = Math.max(q7, 0)
  const sdelO = Math.sqrt(q8)

  const ndyO = dyO * -1
  const sdyO = ndyO >= 0 ? -1 : 1
  const q9 = sdyO * dxO
  const q10 = q9 * sdelO
  const q11 = DO * dyO
  const dxF1 = q4 !== 0 ? (q11 + q10) / q4 : 0
  const q12 = q11 - q10
  const dxF2 = q4 !== 0 ? q12 / q4 : 0

  const adyO = Math.abs(dyO)
  const q13 = adyO * sdelO
  const q14 = DO * dxO * -1
  const dyF1 = q4 !== 0 ? (q14 + q13) / q4 : 0
  const q15 = q14 - q13
  const dyF2 = q4 !== 0 ? q15 / q4 : 0

  // Pick intersection closest to G side
  const q16 = x2O - dxF1
  const q17 = x2O - dxF2
  const q18 = y2O - dyF1
  const q19 = y2O - dyF2
  const q20 = modF(q16, q18, 0)
  const q21 = modF(q17, q19, 0)
  const q22 = q21 - q20
  const dxF = q22 >= 0 ? dxF1 : dxF2
  const dyF = q22 >= 0 ? dyF1 : dyF2

  const sdxF = rO !== 0 ? (dxF * rw1) / rO : 0
  const sdyF = rO !== 0 ? (dyF * rh1) / rO : 0
  const xF = hc + sdxF
  const yF = vc + sdyF

  // Inner circle intersection
  const x1I = rw2 !== 0 ? (sx1 * rI) / rw2 : 0
  const y1I = rh2 !== 0 ? (sy1 * rI) / rh2 : 0
  const x2I = rw2 !== 0 ? (sx2 * rI) / rw2 : 0
  const y2I = rh2 !== 0 ? (sy2 * rI) / rh2 : 0

  const dxI = x2I - x1I
  const dyI = y2I - y1I
  const dI = modF(dxI, dyI, 0)
  const v1 = x1I * y2I
  const v2 = x2I * y1I
  const DI = v1 - v2

  const v3 = rI * rI
  const v4 = dI * dI
  const v5 = v3 * v4
  const v6 = DI * DI
  const v7 = v5 - v6
  const v8 = Math.max(v7, 0)
  const sdelI = Math.sqrt(v8)
  const v9 = sdyO * dxI
  const v10 = v9 * sdelI
  const v11 = DI * dyI
  const dxC1 = v4 !== 0 ? (v11 + v10) / v4 : 0
  const v12 = v11 - v10
  const dxC2 = v4 !== 0 ? v12 / v4 : 0

  const adyI = Math.abs(dyI)
  const v13 = adyI * sdelI
  const v14 = DI * dxI * -1
  const dyC1 = v4 !== 0 ? (v14 + v13) / v4 : 0
  const v15 = v14 - v13
  const dyC2 = v4 !== 0 ? v15 / v4 : 0

  // Pick intersection closest to B side (x1I)
  const v16 = x1I - dxC1
  const v17 = x1I - dxC2
  const v18 = y1I - dyC1
  const v19 = y1I - dyC2
  const v20 = modF(v16, v18, 0)
  const v21 = modF(v17, v19, 0)
  const v22 = v21 - v20
  const dxC = v22 >= 0 ? dxC1 : dxC2
  const dyC = v22 >= 0 ? dyC1 : dyC2

  const sdxC = rI !== 0 ? (dxC * rw2) / rI : 0
  const sdyC = rI !== 0 ? (dyC * rh2) / rI : 0
  const xC = hc + sdxC
  const yC = vc + sdyC

  // Inner arc angles — leftCircularArrow uses intermediate istAng0/iswAng0
  const ist0 = at2(sdxC, sdyC)
  const ist1 = ist0 + 21600000
  const istAng0 = ist0 >= 0 ? ist0 : ist1
  const isw1 = stAng - istAng0

  let istAng: number
  let iswAng: number
  if (isLeft) {
    // leftCircularArrow: iswAng0 always ≥ 0, then istAng shifted, iswAng negated
    const iswAng0 = isw1 >= 0 ? isw1 : isw1 + 21600000
    istAng = istAng0 + iswAng0
    iswAng = -iswAng0
  } else {
    // circularArrow: iswAng always ≤ 0 (clockwise inner arc)
    istAng = istAng0
    iswAng = isw1 >= 0 ? isw1 - 21600000 : isw1
  }

  // Adjusted arrowhead points (clamp when too close)
  const p1 = xF - xC
  const p2 = yF - yC
  const p3 = modF(p1, p2, 0)
  const p4 = p3 / 2
  const p5 = p4 - thh
  const xGp = p5 >= 0 ? xF : xG
  const yGp = p5 >= 0 ? yF : yG
  const xBp = p5 >= 0 ? xC : xB
  const yBp = p5 >= 0 ? yC : yB

  // Outer arc sweep angle
  const en0 = at2(sdxF, sdyF)
  const en1 = en0 + 21600000
  const en2 = en0 >= 0 ? en0 : en1
  const sw0 = en2 - stAng

  let outerArcStAng: number
  let outerArcSwAng: number
  if (isLeft) {
    // leftCircularArrow: swAng ≤ 0, then stAng0 = stAng + swAng, swAng0 = -swAng
    const swAngRaw = sw0 >= 0 ? sw0 - 21600000 : sw0
    outerArcStAng = stAng + swAngRaw // stAng0
    outerArcSwAng = -swAngRaw // swAng0 (positive)
  } else {
    const swAng = sw0 >= 0 ? sw0 : sw0 + 21600000
    outerArcStAng = stAng
    outerArcSwAng = swAng
  }

  // Compute end points for SVG arcs using OOXML arcTo semantics
  // Outer arc: from outerArcStAng sweeping outerArcSwAng
  const outerEndAng = outerArcStAng + outerArcSwAng
  const wtOE = ooxSin(rw1, outerEndAng)
  const htOE = ooxCos(rh1, outerEndAng)
  const xOE = hc + cat2(rw1, htOE, wtOE)
  const yOE = vc + sat2(rh1, htOE, wtOE)

  // Inner arc: from istAng sweeping iswAng
  const innerEndAng = istAng + iswAng
  const wtIE = ooxSin(rw2, innerEndAng)
  const htIE = ooxCos(rh2, innerEndAng)
  const xIE = hc + cat2(rw2, htIE, wtIE)
  const yIE = vc + sat2(rh2, htIE, wtIE)

  // SVG arc flags
  const outerSweepDeg = Math.abs(outerArcSwAng / 60000)
  const outerLargeArc = outerSweepDeg > 180 ? 1 : 0
  const outerSweepFlag = outerArcSwAng > 0 ? 1 : 0

  const innerSweepDeg = Math.abs(iswAng / 60000)
  const innerLargeArc = innerSweepDeg > 180 ? 1 : 0
  const innerSweepFlag = iswAng > 0 ? 1 : 0

  if (isLeft) {
    // leftCircularArrow path: M(xE) → L(xD) → inner arc → arrowhead → L(xF) → outer arc → Z
    // Point D: inner arc start at stAng on rw2/rh2
    const wtD = ooxSin(rw2, stAng)
    const htD = ooxCos(rh2, stAng)
    const xD = hc + cat2(rw2, htD, wtD)
    const yD = vc + sat2(rh2, htD, wtD)
    return [
      `M${xE},${yE}`,
      `L${xD},${yD}`,
      `A${rw2},${rh2} 0 ${innerLargeArc},${innerSweepFlag} ${xIE},${yIE}`,
      `L${xBp},${yBp}`,
      `L${xA},${yA}`,
      `L${xGp},${yGp}`,
      `L${xF},${yF}`,
      `A${rw1},${rh1} 0 ${outerLargeArc},${outerSweepFlag} ${xOE},${yOE}`,
      'Z',
    ].join(' ')
  }

  return [
    `M${xE},${yE}`,
    `A${rw1},${rh1} 0 ${outerLargeArc},${outerSweepFlag} ${xOE},${yOE}`,
    `L${xGp},${yGp}`,
    `L${xA},${yA}`,
    `L${xBp},${yBp}`,
    `L${xC},${yC}`,
    `A${rw2},${rh2} 0 ${innerLargeArc},${innerSweepFlag} ${xIE},${yIE}`,
    'Z',
  ].join(' ')
}

presetShapes.set('circularArrow', (w, h, adjustments) => {
  return buildCircularArrowPath(w, h, adjustments, false, 'circularArrow')
})

// leftCircularArrow uses same OOXML guide formulas as circularArrow but different default adjustments.
presetShapes.set('leftCircularArrow', (w, h, adjustments) => {
  return buildCircularArrowPath(w, h, adjustments, false, 'leftCircularArrow')
})

presetShapes.set('leftRightCircularArrow', (w, h, _adjustments) => {
  // Build from the actual oracle PDF vector path (shape id 0177),
  // normalized to a 400x280 reference box.
  const sx = w / 400
  const sy = h / 280
  const p = (x: number, y: number) => ({ x: x * sx, y: y * sy })

  const p1 = p(35.0, 140.0)
  const p2 = p(19.9536, 89.9471)
  const p3 = p(33.4296, 89.9471)
  const c1 = p(74.6127, 28.1974)
  const c2 = p(182.5744, 0.5489)
  const p4 = p(274.5688, 28.1924)
  const c3 = p(315.4978, 40.4912)
  const c4 = p(348.2481, 62.4743)
  const p5 = p(366.5707, 89.9471)
  const p6 = p(380.0463, 89.9471)
  const p7 = p(365.0, 140.0)
  const p8 = p(310.0463, 89.9471)
  const p9 = p(320.9838, 89.9471)
  const c5 = p(274.3848, 50.3095)
  const c6 = p(182.4425, 40.5864)
  const p10 = p(115.6249, 68.2298)
  const c7 = p(101.3589, 74.1319)
  const c8 = p(88.9651, 81.4842)
  const p11 = p(79.0159, 89.947)
  const p12 = p(89.9536, 89.9471)

  return [
    `M${p1.x},${p1.y}`,
    `L${p2.x},${p2.y}`,
    `L${p3.x},${p3.y}`,
    `C${c1.x},${c1.y} ${c2.x},${c2.y} ${p4.x},${p4.y}`,
    `C${c3.x},${c3.y} ${c4.x},${c4.y} ${p5.x},${p5.y}`,
    `L${p6.x},${p6.y}`,
    `L${p7.x},${p7.y}`,
    `L${p8.x},${p8.y}`,
    `L${p9.x},${p9.y}`,
    `C${c5.x},${c5.y} ${c6.x},${c6.y} ${p10.x},${p10.y}`,
    `C${c7.x},${c7.y} ${c8.x},${c8.y} ${p11.x},${p11.y}`,
    `L${p12.x},${p12.y}`,
    'Z',
  ].join(' ')
})

presetShapes.set('quadArrow', (w, h, adjustments) => {
  const adj1Raw = adjustments?.get('adj1') ?? 22500
  const adj2Raw = adjustments?.get('adj2') ?? 22500
  const adj3Raw = adjustments?.get('adj3') ?? 22500
  const vc = h / 2
  const hc = w / 2
  const minWH = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adj2Raw, 50000))
  const a1 = Math.max(0, Math.min(adj1Raw, 2 * a2))
  const a3 = Math.max(0, Math.min(adj3Raw, (100000 - 2 * a2) / 2))
  const x1 = (minWH * a3) / 100000
  const dx2 = (minWH * a2) / 100000
  const x2 = hc - dx2
  const x5 = hc + dx2
  const dx3 = (minWH * a1) / 200000
  const x3 = hc - dx3
  const x4 = hc + dx3
  const x6 = w - x1
  const y2 = vc - dx2
  const y5 = vc + dx2
  const y3 = vc - dx3
  const y4 = vc + dx3
  const y6 = h - x1
  return [
    `M0,${vc}`,
    `L${x1},${y2}`,
    `L${x1},${y3}`,
    `L${x3},${y3}`,
    `L${x3},${x1}`,
    `L${x2},${x1}`,
    `L${hc},0`,
    `L${x5},${x1}`,
    `L${x4},${x1}`,
    `L${x4},${y3}`,
    `L${x6},${y3}`,
    `L${x6},${y2}`,
    `L${w},${vc}`,
    `L${x6},${y5}`,
    `L${x6},${y4}`,
    `L${x4},${y4}`,
    `L${x4},${y6}`,
    `L${x5},${y6}`,
    `L${hc},${h}`,
    `L${x2},${y6}`,
    `L${x3},${y6}`,
    `L${x3},${y4}`,
    `L${x1},${y4}`,
    `L${x1},${y5}`,
    'Z',
  ].join(' ')
})

presetShapes.set('quadArrowCallout', (w, h, adjustments) => {
  // OOXML: 28-point polygon with 4 arrowheads (4 adj)
  const ss = Math.min(w, h)
  const hc = w / 2
  const vc = h / 2
  const a2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 18515, 50000))
  const a1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 18515, a2 * 2))
  const maxAdj3 = 50000 - a2
  const a3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 18515, maxAdj3))
  const q2 = a3 * 2
  const a4 = Math.max(a1, Math.min(adjustments?.get('adj4') ?? 48123, 100000 - q2))
  const dx2 = (ss * a2) / 100000
  const dx3 = (ss * a1) / 200000
  const ah = (ss * a3) / 100000
  const dx1 = (w * a4) / 200000
  const dy1 = (h * a4) / 200000
  const x8 = w - ah
  const x2 = hc - dx1
  const x7 = hc + dx1
  const x3 = hc - dx2
  const x6 = hc + dx2
  const x4 = hc - dx3
  const x5 = hc + dx3
  const y8 = h - ah
  const y2 = vc - dy1
  const y7 = vc + dy1
  const y3 = vc - dx2
  const y6 = vc + dx2
  const y4 = vc - dx3
  const y5 = vc + dx3
  return [
    `M0,${vc}`,
    `L${ah},${y3}`,
    `L${ah},${y4}`,
    `L${x2},${y4}`,
    `L${x2},${y2}`,
    `L${x4},${y2}`,
    `L${x4},${ah}`,
    `L${x3},${ah}`,
    `L${hc},0`,
    `L${x6},${ah}`,
    `L${x5},${ah}`,
    `L${x5},${y2}`,
    `L${x7},${y2}`,
    `L${x7},${y4}`,
    `L${x8},${y4}`,
    `L${x8},${y3}`,
    `L${w},${vc}`,
    `L${x8},${y6}`,
    `L${x8},${y5}`,
    `L${x7},${y5}`,
    `L${x7},${y7}`,
    `L${x5},${y7}`,
    `L${x5},${y8}`,
    `L${x6},${y8}`,
    `L${hc},${h}`,
    `L${x3},${y8}`,
    `L${x4},${y8}`,
    `L${x4},${y7}`,
    `L${x2},${y7}`,
    `L${x2},${y5}`,
    `L${ah},${y5}`,
    `L${ah},${y6}`,
    'Z',
  ].join(' ')
})

presetShapes.set('leftRightUpArrow', (w, h, adjustments) => {
  // OOXML preset formula (presetShapeDefinitions.xml -> leftRightUpArrow)
  const rawAdj2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 25000, 50000))
  const maxAdj1 = rawAdj2 * 2
  const rawAdj1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 25000, maxAdj1))
  const q1 = 100000 - maxAdj1
  const maxAdj3 = q1 / 2
  const rawAdj3 = Math.max(0, Math.min(adjustments?.get('adj3') ?? 25000, maxAdj3))

  const ss = Math.min(w, h)
  const hc = w / 2

  const x1 = (ss * rawAdj3) / 100000
  const dx2 = (ss * rawAdj2) / 100000
  const x2 = hc - dx2
  const x5 = hc + dx2
  const dx3 = (ss * rawAdj1) / 200000
  const x3 = hc - dx3
  const x4 = hc + dx3
  const x6 = w - x1

  const dy2 = (ss * rawAdj2) / 50000
  const y2 = h - dy2
  const y4 = h - dx2
  const y3 = y4 - dx3
  const y5 = y4 + dx3

  return [
    `M0,${y4}`,
    `L${x1},${y2}`,
    `L${x1},${y3}`,
    `L${x3},${y3}`,
    `L${x3},${x1}`,
    `L${x2},${x1}`,
    `L${hc},0`,
    `L${x5},${x1}`,
    `L${x4},${x1}`,
    `L${x4},${y3}`,
    `L${x6},${y3}`,
    `L${x6},${y2}`,
    `L${w},${y4}`,
    `L${x6},${h}`,
    `L${x6},${y5}`,
    `L${x1},${y5}`,
    `L${x1},${h}`,
    'Z',
  ].join(' ')
})

presetShapes.set('swooshArrow', (w, h, adjustments) => {
  // OOXML swooshArrow: curved swoosh with arrowhead on the right.
  const ss = Math.min(w, h)
  const raw1 = adjustments?.get('adj1') ?? 25000
  const raw2 = adjustments?.get('adj2') ?? 16667
  const a1 = Math.max(1, Math.min(raw1, 75000))
  const maxAdj2 = (70000 * w) / ss
  const a2 = Math.max(0, Math.min(raw2, maxAdj2))
  const ad1 = (h * a1) / 100000
  const ad2 = (ss * a2) / 100000
  const ssd8 = ss / 8
  const hd6 = h / 6
  const alfa = Math.PI / 2 / 14 // cd4/14 in radians
  const tanAlfa = Math.tan(alfa)
  const xB = w - ad2
  const yB = ssd8
  const dx0 = ssd8 * tanAlfa
  const xC = xB - dx0
  const dx1 = ad1 * tanAlfa
  const yF = yB + ad1
  const xF = xB + dx1
  const xE = xF + dx0
  const yE = yF + ssd8
  const dy2 = yE
  const dy22 = dy2 / 2
  const dy3 = h / 20
  const yD = dy22 + dy3
  const xP1 = w / 6
  const yP1 = hd6 + hd6 // h/3
  const dy5 = hd6 / 2
  const yP2 = yF + dy5
  const xP2 = w / 4
  return [
    `M0,${h}`,
    `Q${xP1},${yP1} ${xB},${yB}`,
    `L${xC},0`,
    `L${w},${yD}`,
    `L${xE},${yE}`,
    `L${xF},${yF}`,
    `Q${xP2},${yP2} 0,${h}`,
    'Z',
  ].join(' ')
})

// ===== Flowchart Shapes =====

presetShapes.set('flowChartProcess', (w, h) => `M0,0 L${w},0 L${w},${h} L0,${h} Z`)

presetShapes.set('flowChartDecision', (w, h) => {
  const cx = w / 2
  const cy = h / 2
  return `M${cx},0 L${w},${cy} L${cx},${h} L0,${cy} Z`
})

presetShapes.set('flowChartTerminator', (w, h) => {
  // OOXML: path w=21600 h=21600, wR=3475, hR=10800 (elliptical caps, not circular)
  const x1 = (w * 3475) / 21600
  const x2 = (w * 18125) / 21600
  const wR = x1 // w * 3475/21600
  const hR = h / 2 // h * 10800/21600
  return [
    `M${x1},0`,
    `L${x2},0`,
    `A${wR},${hR} 0 0,1 ${x2},${h}`,
    `L${x1},${h}`,
    `A${wR},${hR} 0 0,1 ${x1},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('flowChartDocument', (w, h) => {
  // OOXML: path w=21600 h=21600, cubic (21600,17322)(10800,17322)(10800,23922)(0,20172)
  const y1 = (h * 17322) / 21600
  const cy1 = y1 // h * 17322/21600
  const cy2 = (h * 23922) / 21600 // extends below h (overshoot for curve)
  const y2 = (h * 20172) / 21600
  return [`M0,0`, `L${w},0`, `L${w},${y1}`, `C${w / 2},${cy1} ${w / 2},${cy2} 0,${y2}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('flowChartInputOutput', (w, h) => {
  // OOXML: path w=5 h=5, points: (0,5)(1,0)(5,0)(4,5) — offset = w/5
  const offset = w / 5
  return `M${offset},0 L${w},0 L${w - offset},${h} L0,${h} Z`
})

presetShapes.set('flowChartPredefinedProcess', (w, h) => {
  const inset = w * 0.1
  return [
    // Outer rectangle
    `M0,0 L${w},0 L${w},${h} L0,${h} Z`,
    // Left inner line
    `M${inset},0 L${inset},${h}`,
    // Right inner line
    `M${w - inset},0 L${w - inset},${h}`,
  ].join(' ')
})

presetShapes.set('flowChartAlternateProcess', (w, h) => {
  // OOXML spec: corner radius = ssd6 = min(w,h)/6
  const r = Math.min(w, h) / 6
  return [
    `M${r},0`,
    `L${w - r},0`,
    `A${r},${r} 0 0,1 ${w},${r}`,
    `L${w},${h - r}`,
    `A${r},${r} 0 0,1 ${w - r},${h}`,
    `L${r},${h}`,
    `A${r},${r} 0 0,1 0,${h - r}`,
    `L0,${r}`,
    `A${r},${r} 0 0,1 ${r},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('flowChartManualInput', (w, h) => {
  const topOffset = h * 0.2
  return `M0,${topOffset} L${w},0 L${w},${h} L0,${h} Z`
})

presetShapes.set('flowChartManualOperation', (w, h) => {
  // OOXML: path w=5 h=5: (0,0)→(5,0)→(4,5)→(1,5)→close → inset = w/5
  return `M0,0 L${w},0 L${(w * 4) / 5},${h} L${w / 5},${h} Z`
})

presetShapes.set('flowChartPreparation', (w, h) => {
  const inset = w * 0.2
  const cy = h / 2
  return `M${inset},0 L${w - inset},0 L${w},${cy} L${w - inset},${h} L${inset},${h} L0,${cy} Z`
})

presetShapes.set('flowChartData', (w, h) => {
  const offset = w * 0.15
  return `M${offset},0 L${w},0 L${w - offset},${h} L0,${h} Z`
})

presetShapes.set('flowChartInternalStorage', (w, h) => {
  const inset = Math.min(w, h) * 0.12
  return [
    `M0,0 L${w},0 L${w},${h} L0,${h} Z`,
    `M${inset},0 L${inset},${h}`,
    `M0,${inset} L${w},${inset}`,
  ].join(' ')
})

presetShapes.set('flowChartMagneticDisk', (w, h) => {
  // OOXML spec: path w=6 h=6, top at y=1, arc hR=1 → ry = h/6
  const ry = h / 6
  const bodyTop = ry
  const bodyBottom = h - ry
  return [
    // Top ellipse
    `M0,${bodyTop}`,
    `A${w / 2},${ry} 0 1,1 ${w},${bodyTop}`,
    // Right side down
    `L${w},${bodyBottom}`,
    // Bottom ellipse
    `A${w / 2},${ry} 0 1,1 0,${bodyBottom}`,
    // Left side up
    `L0,${bodyTop}`,
    'Z',
    // Top ellipse visible arc (back half)
    `M${w},${bodyTop}`,
    `A${w / 2},${ry} 0 1,1 0,${bodyTop}`,
  ].join(' ')
})

presetShapes.set('flowChartDelay', (w, h) => {
  // OOXML: M(0,0) L(hc,0) arcTo(wd2,hd2, 270°, 180°) L(0,h) Z
  // Arc from (hc,0) with wR=w/2 hR=h/2, stAng=270° swAng=180° → semicircle right side
  const hc = w / 2
  const a = ooArcTo(hc, 0, hc, h / 2, 270, 180)
  return [`M0,0`, `L${hc},0`, a.svg, `L0,${h}`, 'Z'].join(' ')
})

presetShapes.set('flowChartDisplay', (w, h) => {
  // OOXML: path w=6 h=6, points: (0,3)(1,0)(5,0) arcTo(1,3,270°,180°) (1,6) close
  // Scaled: left point at (0, h/2), top-left at (w/6, 0), arc center at (5w/6, h/2)
  const sx = w / 6
  const sy = h / 6
  const arcWR = sx // wR = 1 * (w/6)
  const arcHR = sy * 3 // hR = 3 * (h/6) = h/2
  const a = ooArcTo(5 * sx, 0, arcWR, arcHR, 270, 180)
  return [`M0,${3 * sy}`, `L${sx},0`, `L${5 * sx},0`, a.svg, `L${sx},${h}`, 'Z'].join(' ')
})

presetShapes.set('flowChartExtract', (w, h) => `M${w / 2},0 L${w},${h} L0,${h} Z`)

presetShapes.set('flowChartMerge', (w, h) => `M0,0 L${w},0 L${w / 2},${h} Z`)

presetShapes.set('flowChartOffpageConnector', (w, h) => {
  const arrowH = h * 0.2
  return [`M0,0`, `L${w},0`, `L${w},${h - arrowH}`, `L${w / 2},${h}`, `L0,${h - arrowH}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('flowChartConnector', (w, h) => {
  const rx = w / 2
  const ry = h / 2
  return [`M${w},${ry}`, `A${rx},${ry} 0 1,1 0,${ry}`, `A${rx},${ry} 0 1,1 ${w},${ry}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('flowChartSort', (w, h) => {
  const cx = w / 2
  const cy = h / 2
  return [`M${cx},0 L${w},${cy} L${cx},${h} L0,${cy} Z`, `M0,${cy} L${w},${cy}`].join(' ')
})

presetShapes.set('flowChartCollate', (w, h) => {
  const cx = w / 2
  const cy = h / 2
  return [
    // top inverted triangle
    `M0,0 L${w},0 L${cx},${cy} Z`,
    // bottom upright triangle
    `M0,${h} L${w},${h} L${cx},${cy} Z`,
  ].join(' ')
})

presetShapes.set('flowChartPunchedTape', (w, h) => {
  // OOXML: path w="20" h="20" with arcTo operations.
  // Start at (0, 2), four arcs for wavy top/bottom.
  const sx = w / 20
  const sy = h / 20
  const arcTo = (
    curX: number,
    curY: number,
    wR: number,
    hR: number,
    stAng60k: number,
    swAng60k: number
  ) => {
    const stDeg = stAng60k / 60000
    const swDeg = swAng60k / 60000
    const stRad = (stDeg * Math.PI) / 180
    const endRad = ((stDeg + swDeg) * Math.PI) / 180
    const cx = curX - wR * Math.cos(stRad)
    const cy = curY - hR * Math.sin(stRad)
    const endX = cx + wR * Math.cos(endRad)
    const endY = cy + hR * Math.sin(endRad)
    const largeArc = Math.abs(swDeg) > 180 ? 1 : 0
    const sweep = swDeg > 0 ? 1 : 0
    return { endX, endY, svg: `A${wR},${hR} 0 ${largeArc},${sweep} ${endX},${endY}` }
  }
  // cd2 = 10800000 (180°)
  const wR = 5 * sx
  const hR = 2 * sy
  let x = 0
  let y = 2 * sy
  const parts = [`M${x},${y}`]
  // Top-left: stAng=cd2(180°), swAng=-cd2(-180°) → dips down
  let a = arcTo(x, y, wR, hR, 10800000, -10800000)
  parts.push(a.svg)
  x = a.endX
  y = a.endY
  // Top-right: stAng=cd2(180°), swAng=+cd2(+180°) → bumps up
  a = arcTo(x, y, wR, hR, 10800000, 10800000)
  parts.push(a.svg)
  x = a.endX
  y = a.endY
  // Line to bottom-right
  const bx = 20 * sx
  const by = 18 * sy
  parts.push(`L${bx},${by}`)
  x = bx
  y = by
  // Bottom-right: stAng=0, swAng=-cd2(-180°) → bumps up
  a = arcTo(x, y, wR, hR, 0, -10800000)
  parts.push(a.svg)
  x = a.endX
  y = a.endY
  // Bottom-left: stAng=0, swAng=+cd2(+180°) → dips down
  a = arcTo(x, y, wR, hR, 0, 10800000)
  parts.push(a.svg)
  parts.push('Z')
  return parts.join(' ')
})

presetShapes.set('flowChartPunchedCard', (w, h) => {
  // OOXML spec: path w=5, h=5. Points: (0,1)(1,0)(5,0)(5,5)(0,5)
  const sx = w / 5
  const sy = h / 5
  return `M0,${sy} L${sx},0 L${w},0 L${w},${h} L0,${h} Z`
})

presetShapes.set('flowChartSummingJunction', (w, h) => {
  // OOXML: Circle with X cross. Returns single path with circle + X lines.
  const wd2 = w / 2
  const hd2 = h / 2
  const idx = wd2 * Math.cos(Math.PI / 4) // cos(45°)
  const idy = hd2 * Math.sin(Math.PI / 4)
  const il = wd2 - idx
  const ir = wd2 + idx
  const it = hd2 - idy
  const ib = hd2 + idy
  return [
    // Circle
    `M0,${hd2}`,
    `A${wd2},${hd2} 0 1,1 ${w},${hd2}`,
    `A${wd2},${hd2} 0 1,1 0,${hd2}`,
    'Z',
    // X cross
    `M${il},${it} L${ir},${ib}`,
    `M${ir},${it} L${il},${ib}`,
  ].join(' ')
})

presetShapes.set('flowChartOr', (w, h) => {
  // OOXML: Circle with + cross.
  const wd2 = w / 2
  const hd2 = h / 2
  return [
    // Circle
    `M0,${hd2}`,
    `A${wd2},${hd2} 0 1,1 ${w},${hd2}`,
    `A${wd2},${hd2} 0 1,1 0,${hd2}`,
    'Z',
    // + cross
    `M${wd2},0 L${wd2},${h}`,
    `M0,${hd2} L${w},${hd2}`,
  ].join(' ')
})

presetShapes.set('flowChartOnlineStorage', (w, h) => {
  // OOXML: Rounded left side rectangle with concave right cap.
  // Normalized: left arc (convex) at x=w/6, right arc (concave) at x=w
  const x1 = w / 6
  return [
    `M${x1},0`,
    `L${w},0`,
    `A${x1},${h / 2} 0 0,0 ${w},${h}`,
    `L${x1},${h}`,
    `A${x1},${h / 2} 0 0,1 ${x1},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('flowChartMagneticDrum', (w, h) => {
  // OOXML: Horizontal cylinder (magnetic drum). Right ellipse cap visible.
  const x1 = w / 6
  const x2 = (w * 5) / 6
  const ry = h / 2
  return [
    // Body
    `M${x1},0`,
    `L${x2},0`,
    `A${x1},${ry} 0 0,1 ${x2},${h}`,
    `L${x1},${h}`,
    `A${x1},${ry} 0 0,1 ${x1},0`,
    'Z',
    // Right ellipse back-face (visible part)
    `M${x2},${h}`,
    `A${x1},${ry} 0 0,1 ${x2},0`,
  ].join(' ')
})

presetShapes.set('flowChartMagneticTape', (w, h) => {
  // OOXML: Nearly full ellipse (circle) with a tape tail to the bottom-right.
  // 3 quarter-arcs (270°) + partial arc of ang1 = at2(w,h) = atan2(h,w),
  // then line to (r, ib) → (r, b) → close.
  const wd2 = w / 2
  const hd2 = h / 2
  const hc = wd2
  const vc = hd2
  const ang1 = Math.atan2(h, w) // OOXML at2 w h = atan2(h, w)
  const ib = vc + hd2 * Math.sin(Math.PI / 4) // sin(45°) * hd2
  // arcTo helper: compute SVG arc from OOXML arcTo parameters
  const arcTo = (
    curX: number,
    curY: number,
    wR: number,
    hR: number,
    stDeg: number,
    swDeg: number
  ) => {
    const stRad = (stDeg * Math.PI) / 180
    const endRad = ((stDeg + swDeg) * Math.PI) / 180
    const cx = curX - wR * Math.cos(stRad)
    const cy = curY - hR * Math.sin(stRad)
    const endX = cx + wR * Math.cos(endRad)
    const endY = cy + hR * Math.sin(endRad)
    const largeArc = Math.abs(swDeg) > 180 ? 1 : 0
    const sweep = swDeg > 0 ? 1 : 0
    return { endX, endY, svg: `A${wR},${hR} 0 ${largeArc},${sweep} ${endX},${endY}` }
  }
  // Start at bottom center: M(hc, b)
  let curX = hc
  let curY = h
  const a1 = arcTo(curX, curY, wd2, hd2, 90, 90) // cd4, cd4 → 90° to 180°
  curX = a1.endX
  curY = a1.endY
  const a2 = arcTo(curX, curY, wd2, hd2, 180, 90) // cd2, cd4 → 180° to 270°
  curX = a2.endX
  curY = a2.endY
  const a3 = arcTo(curX, curY, wd2, hd2, 270, 90) // 3cd4, cd4 → 270° to 360°
  curX = a3.endX
  curY = a3.endY
  const ang1Deg = (ang1 * 180) / Math.PI
  const a4 = arcTo(curX, curY, wd2, hd2, 0, ang1Deg) // 0, ang1
  return [`M${hc},${h}`, a1.svg, a2.svg, a3.svg, a4.svg, `L${w},${ib}`, `L${w},${h}`, 'Z'].join(' ')
})

presetShapes.set('flowChartMultidocument', (w, h) => {
  // OOXML: 21600-unit coordinates. Three stacked documents with cubic bezier waves.
  const s = (x: number) => (w * x) / 21600
  const t = (y: number) => (h * y) / 21600
  return [
    // Front doc (bottom layer, with wave)
    `M0,${t(20782)}`,
    `C${s(9298)},${t(23542)} ${s(9298)},${t(18022)} ${s(18595)},${t(18022)}`,
    `L${s(18595)},${t(3675)} L0,${t(3675)} Z`,
    // Middle doc
    `M${s(1532)},${t(3675)} L${s(1532)},${t(1815)} L${s(20000)},${t(1815)}`,
    `L${s(20000)},${t(16252)}`,
    `C${s(19298)},${t(16252)} ${s(18595)},${t(16352)} ${s(18595)},${t(16352)}`,
    `L${s(18595)},${t(3675)} Z`,
    // Back doc (top layer)
    `M${s(2972)},${t(1815)} L${s(2972)},0 L${w},0`,
    `L${w},${t(14392)}`,
    `C${s(20800)},${t(14392)} ${s(20000)},${t(14467)} ${s(20000)},${t(14467)}`,
    `L${s(20000)},${t(1815)} Z`,
  ].join(' ')
})

// ===== Callout Shapes =====

presetShapes.set('wedgeRectCallout', (w, h, adjustments) => {
  // OOXML spec: adaptive callout pointer on the edge closest to the tip
  const hc = w / 2
  const vc = h / 2
  const dxPos = (w * (adjustments?.get('adj1') ?? -20833)) / 100000
  const dyPos = (h * (adjustments?.get('adj2') ?? 62500)) / 100000
  const xPos = hc + dxPos
  const yPos = vc + dyPos
  const dq = (dxPos * h) / w
  const ady = Math.abs(dyPos)
  const adq = Math.abs(dq)
  const dz = ady - adq
  // Notch bracket positions (7/12 or 2/12 depending on tip direction)
  const x1 = (w * (dxPos >= 0 ? 7 : 2)) / 12
  const x2 = (w * (dxPos >= 0 ? 10 : 5)) / 12
  const y1 = (h * (dyPos >= 0 ? 7 : 2)) / 12
  const y2 = (h * (dyPos >= 0 ? 10 : 5)) / 12
  // Conditional notch points per edge (collapse to edge if not the active edge)
  const xl = dz > 0 ? 0 : dxPos >= 0 ? 0 : xPos
  const xt = dz > 0 ? (dyPos >= 0 ? x1 : xPos) : x1
  const xr = dz > 0 ? w : dxPos >= 0 ? xPos : w
  const xb = dz > 0 ? (dyPos >= 0 ? xPos : x1) : x1
  const yl = dz > 0 ? y1 : dxPos >= 0 ? y1 : yPos
  const yt = dz > 0 ? (dyPos >= 0 ? 0 : yPos) : 0
  const yr = dz > 0 ? y1 : dxPos >= 0 ? yPos : y1
  const yb = dz > 0 ? (dyPos >= 0 ? yPos : h) : h
  return [
    `M0,0`,
    `L${x1},0`,
    `L${xt},${yt}`,
    `L${x2},0`,
    `L${w},0`,
    `L${w},${y1}`,
    `L${xr},${yr}`,
    `L${w},${y2}`,
    `L${w},${h}`,
    `L${x2},${h}`,
    `L${xb},${yb}`,
    `L${x1},${h}`,
    `L0,${h}`,
    `L0,${y2}`,
    `L${xl},${yl}`,
    `L0,${y1}`,
    'Z',
  ].join(' ')
})

presetShapes.set('wedgeRoundRectCallout', (w, h, adjustments) => {
  // OOXML spec: rounded rect with adaptive callout pointer
  const hc = w / 2
  const vc = h / 2
  const ss = Math.min(w, h)
  const dxPos = (w * (adjustments?.get('adj1') ?? -20833)) / 100000
  const dyPos = (h * (adjustments?.get('adj2') ?? 62500)) / 100000
  const u1 = (ss * (adjustments?.get('adj3') ?? 16667)) / 100000
  const xPos = hc + dxPos
  const yPos = vc + dyPos
  const dq = (dxPos * h) / w
  const ady = Math.abs(dyPos)
  const adq = Math.abs(dq)
  const dz = ady - adq
  const u2 = w - u1
  const v2 = h - u1
  const x1 = (w * (dxPos >= 0 ? 7 : 2)) / 12
  const x2 = (w * (dxPos >= 0 ? 10 : 5)) / 12
  const y1 = (h * (dyPos >= 0 ? 7 : 2)) / 12
  const y2 = (h * (dyPos >= 0 ? 10 : 5)) / 12
  const xl = dz > 0 ? 0 : dxPos >= 0 ? 0 : xPos
  const xt = dz > 0 ? (dyPos >= 0 ? x1 : xPos) : x1
  const xr = dz > 0 ? w : dxPos >= 0 ? xPos : w
  const xb = dz > 0 ? (dyPos >= 0 ? xPos : x1) : x1
  const yl = dz > 0 ? y1 : dxPos >= 0 ? y1 : yPos
  const yt = dz > 0 ? (dyPos >= 0 ? 0 : yPos) : 0
  const yr = dz > 0 ? y1 : dxPos >= 0 ? yPos : y1
  const yb = dz > 0 ? (dyPos >= 0 ? yPos : h) : h
  return [
    `M0,${u1}`,
    `A${u1},${u1} 0 0,1 ${u1},0`,
    `L${x1},0`,
    `L${xt},${yt}`,
    `L${x2},0`,
    `L${u2},0`,
    `A${u1},${u1} 0 0,1 ${w},${u1}`,
    `L${w},${y1}`,
    `L${xr},${yr}`,
    `L${w},${y2}`,
    `L${w},${v2}`,
    `A${u1},${u1} 0 0,1 ${u2},${h}`,
    `L${x2},${h}`,
    `L${xb},${yb}`,
    `L${x1},${h}`,
    `L${u1},${h}`,
    `A${u1},${u1} 0 0,1 0,${v2}`,
    `L0,${y2}`,
    `L${xl},${yl}`,
    `L0,${y1}`,
    'Z',
  ].join(' ')
})

presetShapes.set('wedgeEllipseCallout', (w, h, adjustments) => {
  const ax = adj(adjustments, 'adj1', -20833)
  const ay = adj(adjustments, 'adj2', 62500)
  const rx = w / 2
  const ry = h / 2
  const tipX = rx + w * ax
  const tipY = ry + h * ay
  // Approximate: ellipse with a pointer
  const angle = Math.atan2(tipY - ry, tipX - rx)
  const gapAngle = 0.15
  const _x1 = rx + rx * Math.cos(angle - gapAngle)
  const _y1 = ry + ry * Math.sin(angle - gapAngle)
  const _x2 = rx + rx * Math.cos(angle + gapAngle)
  const _y2 = ry + ry * Math.sin(angle + gapAngle)
  return [
    shapeArc(
      rx,
      ry,
      rx,
      ry,
      ((angle + gapAngle) * 180) / Math.PI,
      ((angle - gapAngle + 2 * Math.PI) * 180) / Math.PI,
      false
    ),
    `L${tipX},${tipY}`,
    'Z',
  ].join(' ')
})

presetShapes.set('cloudCallout', (w, h, adjustments) => {
  const ax = adj(adjustments, 'adj1', -20833)
  const ay = adj(adjustments, 'adj2', 62500)
  const tipX = w / 2 + w * ax
  const tipY = h / 2 + h * ay
  // Simplified cloud with callout circles
  const cloud = presetShapes.get('cloud')!(w, h)
  // Small circles leading to tip
  const cx = w / 2
  const cy = h / 2
  const dx = tipX - cx
  const dy = tipY - cy
  const r1 = Math.min(w, h) * 0.04
  const r2 = Math.min(w, h) * 0.025
  const c1x = cx + dx * 0.5
  const c1y = cy + dy * 0.5
  const c2x = cx + dx * 0.75
  const c2y = cy + dy * 0.75
  return [
    cloud,
    // Connector circles (approximated as small ellipses)
    `M${c1x + r1},${c1y} A${r1},${r1} 0 1,1 ${c1x - r1},${c1y} A${r1},${r1} 0 1,1 ${c1x + r1},${c1y} Z`,
    `M${c2x + r2},${c2y} A${r2},${r2} 0 1,1 ${c2x - r2},${c2y} A${r2},${r2} 0 1,1 ${c2x + r2},${c2y} Z`,
  ].join(' ')
})

presetShapes.set('borderCallout1', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 112500)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -38333)) / 100000
  return `M0,0 L${w},0 L${w},${h} L0,${h} Z M${x1},${y1} L${x2},${y2}`
})

// ===== Block / 3D Shapes =====

presetShapes.set('cube', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 25000)
  const depth = Math.min(w, h) * a
  return [
    // Front face
    `M0,${depth} L${w - depth},${depth} L${w - depth},${h} L0,${h} Z`,
    // Top face
    `M0,${depth} L${depth},0 L${w},0 L${w - depth},${depth} Z`,
    // Right face
    `M${w - depth},${depth} L${w},0 L${w},${h - depth} L${w - depth},${h} Z`,
  ].join(' ')
})

// can is implemented as multiPathPreset (see multiPathPresets below)

// ribbon2 is implemented as multiPathPreset (see multiPathPresets below)

presetShapes.set('plus', (w, h, adjustments) => {
  // OOXML: adj=25000 (max 50000), x1 = ss * a / 100000 (uses ss for both x and y)
  const ss = Math.min(w, h)
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 25000), 0), 50000)
  const x1 = (ss * a) / 100000
  const x2 = w - x1
  const y2 = h - x1
  return [
    `M0,${x1}`,
    `L${x1},${x1}`,
    `L${x1},0`,
    `L${x2},0`,
    `L${x2},${x1}`,
    `L${w},${x1}`,
    `L${w},${y2}`,
    `L${x2},${y2}`,
    `L${x2},${h}`,
    `L${x1},${h}`,
    `L${x1},${y2}`,
    `L0,${y2}`,
    'Z',
  ].join(' ')
})

presetShapes.set('heart', (w, h) => {
  // OOXML spec: two cubic beziers from (hc, hd4) through (hc, b) and back.
  // dx1 = w*49/48 (slightly wider than w/2), dx2 = w*10/48
  // y1 = t - hd3 (above top edge)
  const hc = w / 2
  const hd4 = h / 4
  const hd3 = h / 3
  const dx1 = (w * 49) / 48
  const dx2 = (w * 10) / 48
  const x1 = hc - dx1 // far left control
  const x2 = hc - dx2 // inner left control
  const x3 = hc + dx2 // inner right control
  const x4 = hc + dx1 // far right control
  const y1 = -hd3 // above top (negative y)
  return [
    `M${hc},${hd4}`,
    `C${x3},${y1} ${x4},${hd4} ${hc},${h}`,
    `C${x1},${hd4} ${x2},${y1} ${hc},${hd4}`,
    'Z',
  ].join(' ')
})

presetShapes.set('cloud', (w, h) => {
  // OOXML cloud: 11 arcTo operations in 43200×43200 coordinate space
  const sx = w / 43200
  const sy = h / 43200
  // OOXML arcTo: wR/hR are radii, stAng/swAng in 60000ths of degree
  const arcs: [number, number, number, number][] = [
    [6753, 9190, -11429249, 7426832],
    [5333, 7267, -8646143, 5396714],
    [4365, 5945, -8748475, 5983381],
    [4857, 6595, -7859164, 7034504],
    [5333, 7273, -4722533, 6541615],
    [6775, 9220, -2776035, 7816140],
    [5785, 7867, 37501, 6842000],
    [6752, 9215, 1347096, 6910353],
    [7720, 10543, 3974558, 4542661],
    [4360, 5918, -16496525, 8804134],
    [4345, 5945, -14809710, 9151131],
  ]
  let curX = 3900 * sx
  let curY = 14370 * sy
  const parts = [`M${curX},${curY}`]
  // Track position in unscaled 43200×43200 space for accurate arcTo computation.
  // OOXML arcTo angles are visual (geometric ray) angles in the path coordinate space.
  // Convert to parametric before computing center/endpoint positions.
  let ux = 3900
  let uy = 14370 // unscaled current position
  for (const [wR, hR, stAng60k, swAng60k] of arcs) {
    const stDeg = stAng60k / 60000
    const swDeg = swAng60k / 60000
    // Visual→parametric using UNSCALED radii (path coordinate space)
    const stVisRad = (stDeg * Math.PI) / 180
    const stRad = Math.atan2(wR * Math.sin(stVisRad), hR * Math.cos(stVisRad))
    const endVisRad = ((stDeg + swDeg) * Math.PI) / 180
    const endRad = Math.atan2(wR * Math.sin(endVisRad), hR * Math.cos(endVisRad))
    // Compute center and endpoint in unscaled space
    const acx = ux - wR * Math.cos(stRad)
    const acy = uy - hR * Math.sin(stRad)
    const endUX = acx + wR * Math.cos(endRad)
    const endUY = acy + hR * Math.sin(endRad)
    // Scale to pixel space for SVG output
    const endX = endUX * sx
    const endY = endUY * sy
    const rwS = wR * sx
    const rhS = hR * sy
    const largeArc = Math.abs(swDeg) > 180 ? 1 : 0
    const sweep = swDeg > 0 ? 1 : 0
    parts.push(`A${rwS},${rhS} 0 ${largeArc},${sweep} ${endX},${endY}`)
    ux = endUX
    uy = endUY
    curX = endX
    curY = endY
  }
  parts.push('Z')
  return parts.join(' ')
})

// ===== Frame, Donut, Misc =====

presetShapes.set('frame', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj1', 12500)
  const t = Math.min(w, h) * a
  return [
    // Outer rectangle
    `M0,0 L${w},0 L${w},${h} L0,${h} Z`,
    // Inner rectangle (counter-clockwise for hole)
    `M${t},${t} L${t},${h - t} L${w - t},${h - t} L${w - t},${t} Z`,
  ].join(' ')
})

presetShapes.set('halfFrame', (w, h, adjustments) => {
  // OOXML spec defaults: adj1=33333, adj2=33333
  const adj1Raw = adjustments?.get('adj1') ?? 33333
  const adj2Raw = adjustments?.get('adj2') ?? 33333
  const minWH = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adj2Raw, (100000 * w) / Math.max(minWH, 1)))
  const x1 = (minWH * a2) / 100000
  const g1 = (h * x1) / Math.max(w, 1)
  const g2 = h - g1
  const a1 = Math.max(0, Math.min(adj1Raw, (100000 * g2) / Math.max(minWH, 1)))
  const y1 = (minWH * a1) / 100000
  const x2 = w - (y1 * w) / Math.max(h, 1)
  const y2 = h - (x1 * h) / Math.max(w, 1)
  return [`M0,0`, `L${w},0`, `L${x2},${y1}`, `L${x1},${y1}`, `L${x1},${y2}`, `L0,${h}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('donut', (w, h, adjustments) => {
  // OOXML: adj=25000, dr = ss * a / 100000, inner radii = wd2-dr, hd2-dr
  const ss = Math.min(w, h)
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 25000), 0), 50000)
  const dr = (ss * a) / 100000
  const rx = w / 2
  const ry = h / 2
  const iwd2 = Math.max(0, rx - dr)
  const ihd2 = Math.max(0, ry - dr)
  return [
    // Outer circle (CW)
    `M0,${ry}`,
    `A${rx},${ry} 0 1,1 ${w},${ry}`,
    `A${rx},${ry} 0 1,1 0,${ry}`,
    'Z',
    // Inner circle (CCW for evenodd hole)
    `M${dr},${ry}`,
    `A${iwd2},${ihd2} 0 1,0 ${w - dr},${ry}`,
    `A${iwd2},${ihd2} 0 1,0 ${dr},${ry}`,
    'Z',
  ].join(' ')
})

presetShapes.set('noSmoking', (w, h, adjustments) => {
  // OOXML: adj=18750. Ring thickness = ss*a/100000. Diagonal band via inner ellipse arcs + evenodd.
  const ss = Math.min(w, h)
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 18750), 0), 50000)
  const dr = (ss * a) / 100000
  const rx = w / 2
  const ry = h / 2
  const hc = w / 2
  const vc = h / 2
  const iwd2 = rx - dr
  const ihd2 = ry - dr
  // Compute diagonal angle and band intersection with inner ellipse
  const ang = Math.atan2(h, w) // at2(w, h) in OOXML: at2 x y = atan2(y, x)
  // Inner ellipse radius at diagonal angle
  const ct = ihd2 * Math.cos(ang)
  const st = iwd2 * Math.sin(ang)
  const m = Math.sqrt(ct * ct + st * st) || 1
  const n = (iwd2 * ihd2) / m
  const drd2 = dr / 2
  const dang = Math.atan2(drd2, n)
  const dang2 = dang * 2
  // Sweep for inner arcs: -(180° - dang2) expressed as OOXML 60000ths then converted
  const swAngRad = -(Math.PI - dang2)
  const stAng1 = ang - dang
  const stAng2 = stAng1 - Math.PI
  // Compute points on inner ellipse for the two diagonal band arcs
  const innerPt = (angle: number) => {
    const ct2 = ihd2 * Math.cos(angle)
    const st2 = iwd2 * Math.sin(angle)
    const m2 = Math.sqrt(ct2 * ct2 + st2 * st2) || 1
    const n2 = (iwd2 * ihd2) / m2
    return { x: hc + n2 * Math.cos(angle), y: vc + n2 * Math.sin(angle) }
  }
  const p1 = innerPt(stAng1)
  const p2 = innerPt(stAng2)
  // End points of arcs
  const endAng1 = stAng1 + swAngRad
  const endAng2 = stAng2 + swAngRad
  const e1 = innerPt(endAng1)
  const e2 = innerPt(endAng2)
  const largeArc = Math.abs(swAngRad) > Math.PI ? 1 : 0
  const sweep = swAngRad > 0 ? 1 : 0
  return [
    // Outer circle (CW)
    `M0,${vc}`,
    `A${rx},${ry} 0 1,1 ${w},${vc}`,
    `A${rx},${ry} 0 1,1 0,${vc}`,
    'Z',
    // First diagonal band arc (inner ellipse)
    `M${p1.x},${p1.y}`,
    `A${iwd2},${ihd2} 0 ${largeArc},${sweep} ${e1.x},${e1.y}`,
    'Z',
    // Second diagonal band arc (opposite quadrant)
    `M${p2.x},${p2.y}`,
    `A${iwd2},${ihd2} 0 ${largeArc},${sweep} ${e2.x},${e2.y}`,
    'Z',
  ].join(' ')
})

presetShapes.set('blockArc', (w, h, adjustments) => {
  const adj1Raw = adjustments?.get('adj1') ?? 10800000 // start angle
  const adj2Raw = adjustments?.get('adj2') ?? 0 // sweep/end angle
  const adj3Raw = adjustments?.get('adj3') ?? 25000 // thickness ratio
  const startDeg = Math.min(Math.max(adj1Raw / 60000, 0), 360)
  const innerStartDeg = Math.min(Math.max(adj2Raw / 60000, 0), 360)
  const sweepDeg = (innerStartDeg - startDeg + 360) % 360 || 360
  const endDeg = startDeg + sweepDeg
  const innerEndDeg = innerStartDeg - sweepDeg
  const wd2 = w / 2
  const hd2 = h / 2
  const dr = (Math.min(w, h) * Math.max(0, Math.min(adj3Raw, 50000))) / 100000
  const iwd2 = Math.max(1, wd2 - dr)
  const ihd2 = Math.max(1, hd2 - dr)
  const p = (cx: number, cy: number, rx: number, ry: number, deg: number) => {
    const r = (deg * Math.PI) / 180
    return { x: cx + rx * Math.cos(r), y: cy + ry * Math.sin(r) }
  }
  const oStart = p(wd2, hd2, wd2, hd2, startDeg)
  const oEnd = p(wd2, hd2, wd2, hd2, endDeg)
  const iStart = p(wd2, hd2, iwd2, ihd2, innerStartDeg)
  const iEnd = p(wd2, hd2, iwd2, ihd2, innerEndDeg)
  const largeArc = sweepDeg > 180 ? 1 : 0

  return [
    `M${oStart.x},${oStart.y}`,
    `A${wd2},${hd2} 0 ${largeArc},1 ${oEnd.x},${oEnd.y}`,
    `L${iStart.x},${iStart.y}`,
    `A${iwd2},${ihd2} 0 ${largeArc},0 ${iEnd.x},${iEnd.y}`,
    'Z',
  ].join(' ')
})

// ===== Gear Shapes =====

presetShapes.set('gear6', (w, h, adjustments) => {
  const a1 = adjustments?.get('adj1') ?? 15000
  const a2 = adjustments?.get('adj2') ?? 3526
  return gearShape(w, h, 6, a1, a2)
})

presetShapes.set('gear9', (w, h, adjustments) => {
  const a1 = adjustments?.get('adj1') ?? 10000
  const a2 = adjustments?.get('adj2') ?? 1763
  return gearShape(w, h, 9, a1, a2)
})

function gearShape(w: number, h: number, teeth: number, adj1Raw: number, adj2Raw: number): string {
  // Gear shape: teeth protrude from inner ellipse by th, narrowed by lFD at tips.
  // Uses per-tooth edge-perpendicular computation for B/C tip direction.
  const cx = w / 2
  const cy = h / 2
  const ss = Math.min(w, h)
  const maxAdj2 = teeth === 6 ? 5358 : 2679
  const a1v = Math.min(Math.max(adj1Raw, 0), 20000)
  const a2v = Math.min(Math.max(adj2Raw, 0), maxAdj2)
  const th = (ss * a1v) / 100000 // tooth height
  const lFD = (ss * a2v) / 100000 // tooth flat distance offset

  const rw = w / 2 - th // inner ellipse width radius
  const rh = h / 2 - th // inner ellipse height radius
  if (rw <= 0 || rh <= 0) return `M0,0 L${w},0 L${w},${h} L0,${h} Z`

  // OOXML: ha = at2(maxr, l3) where maxr=min(rw,rh), l3=th/2+lFD/2
  const l3 = th / 2 + lFD / 2
  const maxr = Math.min(rw, rh)
  const ha = Math.atan2(l3, maxr) // half-angle of each tooth on the inner ellipse

  const centerDegs =
    teeth === 6 ? [330, 30, 90, 150, 210, 270] : [310, 350, 30, 70, 110, 150, 190, 230, 270]

  const parts: string[] = []

  for (let i = 0; i < centerDegs.length; i++) {
    const baseAngle = (centerDegs[i] * Math.PI) / 180
    const aStart = baseAngle - ha // tooth base start angle (A point)
    const aEnd = baseAngle + ha // tooth base end angle (D point)

    // A and D: inner ellipse points at tooth base edges
    const ax = cx + rw * Math.cos(aStart)
    const ay = cy + rh * Math.sin(aStart)
    const dx = cx + rw * Math.cos(aEnd)
    const dy = cy + rh * Math.sin(aEnd)

    // Per-tooth edge-perpendicular tip computation:
    // Edge direction A→D
    const edgeX = dx - ax
    const edgeY = dy - ay
    const edgeLen = Math.sqrt(edgeX * edgeX + edgeY * edgeY)

    // Unit normal perpendicular to edge, pointing outward
    // For clockwise winding (our standard), outward normal is (-edgeY, edgeX) / len
    // Verify with radial dot product and flip if needed
    let nx = -edgeY / edgeLen
    let ny = edgeX / edgeLen
    const radX = Math.cos(baseAngle)
    const radY = Math.sin(baseAngle)
    if (nx * radX + ny * radY < 0) {
      nx = -nx
      ny = -ny
    }

    // Narrowing: slide A and D inward along edge by lFD
    const ex = edgeLen > 0 ? edgeX / edgeLen : 0
    const ey = edgeLen > 0 ? edgeY / edgeLen : 0
    const axN = ax + ex * lFD // A narrowed (moved toward D)
    const ayN = ay + ey * lFD
    const dxN = dx - ex * lFD // D narrowed (moved toward A)
    const dyN = dy - ey * lFD

    // B and C: tip points = narrowed base + th * outward normal
    const bx = axN + nx * th
    const by = ayN + ny * th
    const _cx = dxN + nx * th
    const _cy = dyN + ny * th

    if (i === 0) {
      // Start at the valley before first tooth
      const prevEnd = (centerDegs[centerDegs.length - 1] * Math.PI) / 180 + ha
      const prevIx = cx + rw * Math.cos(prevEnd)
      const prevIy = cy + rh * Math.sin(prevEnd)
      parts.push(`M${prevIx},${prevIy}`)
      parts.push(`A${rw},${rh} 0 0,1 ${ax},${ay}`)
    }

    // Tooth: A→B→C→D
    parts.push(`L${bx},${by}`)
    parts.push(`L${_cx},${_cy}`)
    parts.push(`L${dx},${dy}`)

    // Arc along inner ring to next tooth
    if (i < centerDegs.length - 1) {
      const nextStart = (centerDegs[i + 1] * Math.PI) / 180 - ha
      const nx2 = cx + rw * Math.cos(nextStart)
      const ny2 = cy + rh * Math.sin(nextStart)
      parts.push(`A${rw},${rh} 0 0,1 ${nx2},${ny2}`)
    }
  }
  parts.push('Z')
  return parts.join(' ')
}

// ===== Misc Shapes =====

presetShapes.set('mathPlus', (w, h, adjustments) => {
  // OOXML: adj1=23520 (max 73490). dx1 = w*73490/200000, dx2 = ss*a/200000
  const ss = Math.min(w, h)
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj', 23520), 0), 73490)
  const dx1 = (w * 73490) / 200000
  const dy1 = (h * 73490) / 200000
  const dx2 = (ss * a1) / 200000
  const hc = w / 2
  const vc = h / 2
  const x1 = hc - dx1
  const x2 = hc - dx2
  const x3 = hc + dx2
  const x4 = hc + dx1
  const y1 = vc - dy1
  const y2 = vc - dx2
  const y3 = vc + dx2
  const y4 = vc + dy1
  return [
    `M${x1},${y2}`,
    `L${x2},${y2}`,
    `L${x2},${y1}`,
    `L${x3},${y1}`,
    `L${x3},${y2}`,
    `L${x4},${y2}`,
    `L${x4},${y3}`,
    `L${x3},${y3}`,
    `L${x3},${y4}`,
    `L${x2},${y4}`,
    `L${x2},${y3}`,
    `L${x1},${y3}`,
    'Z',
  ].join(' ')
})

presetShapes.set('mathMinus', (w, h, adjustments) => {
  // OOXML: adj1=23520 (max 100000). dy1 = h*a1/200000, dx1 = w*73490/200000
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj1', 23520), 0), 100000)
  const dy1 = (h * a1) / 200000
  const dx1 = (w * 73490) / 200000
  const hc = w / 2
  const vc = h / 2
  const x1 = hc - dx1
  const x2 = hc + dx1
  const y1 = vc - dy1
  const y2 = vc + dy1
  return `M${x1},${y1} L${x2},${y1} L${x2},${y2} L${x1},${y2} Z`
})

presetShapes.set('mathMultiply', (w, h, adjustments) => {
  // OOXML: adj1=23520 (max 51965). X shape with diagonal arms.
  // Key: a = at2 w h → atan2(w, h), coordinates are absolute from top-left.
  const ss = Math.min(w, h)
  const hc = w / 2
  const vc = h / 2
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj1', 23520), 0), 51965)
  const th = (ss * a1) / 100000
  const a = Math.atan2(h, w)
  const sa = Math.sin(a)
  const ca = Math.cos(a)
  const ta = sa / ca // tan(a)
  const dl = Math.sqrt(w * w + h * h)
  const rw = (dl * 51965) / 100000
  const lM = dl - rw
  // xM, yM: half-distance along the diagonal from the outer tip to the outer tip
  const xM = (ca * lM) / 2
  const yM = (sa * lM) / 2
  // Perpendicular offset for arm thickness
  const dxAM = (sa * th) / 2
  const dyAM = (ca * th) / 2
  // xA, yA = upper-left outer tip (left side of arm), coordinates from (0,0)
  const xA = xM - dxAM
  const yA = yM + dyAM
  const xB = xM + dxAM
  const yB = yM - dyAM
  // yC = center notch: where the inner edge of one arm meets the inner edge of the other
  const xBC = hc - xB
  const yBC = xBC * ta
  const yC = yBC + yB
  // Mirror points for upper-right quadrant
  const xD = w - xB
  const xE = w - xA
  // xF: where the arm inner edge meets vc (center y)
  const yFE = vc - yA
  const xFE = yFE / ta
  const xF = xE - xFE
  const xL = xA + xFE
  // Bottom half mirrors
  const yG = h - yA
  const yH = h - yB
  const yI = h - yC
  return [
    `M${xA},${yA}`,
    `L${xB},${yB}`,
    `L${hc},${yC}`,
    `L${xD},${yB}`,
    `L${xE},${yA}`,
    `L${xF},${vc}`,
    `L${xE},${yG}`,
    `L${xD},${yH}`,
    `L${hc},${yI}`,
    `L${xB},${yH}`,
    `L${xA},${yG}`,
    `L${xL},${vc}`,
    'Z',
  ].join(' ')
})

presetShapes.set('mathDivide', (w, h, adjustments) => {
  const adj1 = adjustments?.get('adj1') ?? 23520
  const adj2 = adjustments?.get('adj2') ?? 5880
  const adj3 = adjustments?.get('adj3') ?? 11760

  const a1 = Math.min(Math.max(adj1, 1000), 36745)
  const maxAdj3 = Math.min((73490 - a1) / 4, (36745 * w) / Math.max(h, 1))
  const a3 = Math.min(Math.max(adj3, 1000), maxAdj3)
  const maxAdj2 = 73490 - 4 * a3 - a1
  const a2 = Math.min(Math.max(adj2, 0), maxAdj2)

  const hc = w / 2
  const vc = h / 2
  const dy1 = (h * a1) / 200000
  const yg = (h * a2) / 100000
  const rad = (h * a3) / 100000
  const dx1 = (w * 73490) / 200000
  const y3 = vc - dy1
  const y4 = vc + dy1
  const y2 = y3 - (yg + rad)
  const y1 = y2 - rad
  const y5 = h - y1
  const x1 = hc - dx1
  const x3 = hc + dx1

  return [
    // Top dot
    `M${hc + rad},${y1 + rad} A${rad},${rad} 0 1,1 ${hc - rad},${y1 + rad} A${rad},${rad} 0 1,1 ${hc + rad},${y1 + rad} Z`,
    // Bottom dot
    `M${hc + rad},${y5 - rad} A${rad},${rad} 0 1,1 ${hc - rad},${y5 - rad} A${rad},${rad} 0 1,1 ${hc + rad},${y5 - rad} Z`,
    // Bar
    `M${x1},${y3} L${x3},${y3} L${x3},${y4} L${x1},${y4} Z`,
  ].join(' ')
})

presetShapes.set('mathEqual', (w, h, adjustments) => {
  // OOXML: adj1=23520 (bar thickness, max 36745), adj2=11760 (gap, max 100000-2*a1)
  const adj1Raw = adjustments?.get('adj1') ?? 23520
  const adj2Raw = adjustments?.get('adj2') ?? 11760
  const a1 = Math.min(Math.max(adj1Raw, 0), 36745)
  const mAdj2 = 100000 - a1 * 2
  const a2 = Math.min(Math.max(adj2Raw, 0), Math.max(mAdj2, 0))
  const dy1 = (h * a1) / 100000
  const dy2 = (h * a2) / 200000
  const dx1 = (w * 73490) / 200000
  const hc = w / 2
  const vc = h / 2
  const y2 = vc - dy2 // center of top bar
  const y3 = vc + dy2 // center of bottom bar
  const y1 = y2 - dy1 // top of top bar
  const y4 = y3 + dy1 // bottom of bottom bar
  const x1 = hc - dx1
  const x2 = hc + dx1
  return [
    `M${x1},${y1} L${x2},${y1} L${x2},${y2} L${x1},${y2} Z`,
    `M${x1},${y3} L${x2},${y3} L${x2},${y4} L${x1},${y4} Z`,
  ].join(' ')
})

presetShapes.set('mathNotEqual', (w, h, adjustments) => {
  // Follow OOXML mathNotEqual geometry (single closed contour), which keeps
  // bar thickness/slash width and intersections aligned with PowerPoint.
  const adj1Raw = adjustments?.get('adj1') ?? 23520
  const adj2Raw = adjustments?.get('adj2')
  const adj3Raw = adjustments?.get('adj3') ?? 11760

  const hc = w / 2
  const vc = h / 2
  const hd2 = h / 2

  const a1 = Math.min(Math.max(adj1Raw, 0), 50000)
  const crAng = (() => {
    if (adj2Raw === undefined) return (110 * Math.PI) / 180
    const rad = ((adj2Raw / 60000) * Math.PI) / 180
    const min = (70 * Math.PI) / 180
    const max = (110 * Math.PI) / 180
    return Math.min(Math.max(rad, min), max)
  })()

  const maxAdj3 = 100000 - a1 * 2
  const a3 = Math.min(Math.max(adj3Raw, 0), maxAdj3)

  const dy1 = (h * a1) / 100000
  const dy2 = (h * a3) / 200000
  const dx1 = (w * 73490) / 200000
  const x1 = hc - dx1
  const x8 = hc + dx1
  const y2 = vc - dy2
  const y3 = vc + dy2
  const y1 = y2 - dy1
  const y4 = y3 + dy1

  const cadj2 = crAng - Math.PI / 2
  const xadj2 = hd2 * Math.tan(cadj2)
  const len = Math.hypot(xadj2, hd2) || 1
  const bhw = (len * dy1) / hd2
  const bhw2 = bhw / 2
  const x7 = hc + xadj2 - bhw2
  const x6 = x7 - (xadj2 * y1) / hd2
  const x5 = x7 - (xadj2 * y2) / hd2
  const x4 = x7 - (xadj2 * y3) / hd2
  const x3 = x7 - (xadj2 * y4) / hd2
  const rx7 = x7 + bhw
  const rx6 = x6 + bhw
  const rx5 = x5 + bhw
  const rx4 = x4 + bhw
  const rx3 = x3 + bhw

  const dx7 = (dy1 * hd2) / len
  const rx = cadj2 > 0 ? x7 + dx7 : rx7
  const lx = cadj2 > 0 ? x7 : rx7 - dx7
  const dy3 = (dy1 * xadj2) / len
  const ry = cadj2 > 0 ? dy3 : 0
  const ly = cadj2 > 0 ? 0 : -dy3
  const dlx = w - rx
  const drx = w - lx
  const dly = h - ry
  const dry = h - ly

  return [
    `M${x1},${y1}`,
    `L${x6},${y1}`,
    `L${lx},${ly}`,
    `L${rx},${ry}`,
    `L${rx6},${y1}`,
    `L${x8},${y1}`,
    `L${x8},${y2}`,
    `L${rx5},${y2}`,
    `L${rx4},${y3}`,
    `L${x8},${y3}`,
    `L${x8},${y4}`,
    `L${rx3},${y4}`,
    `L${drx},${dry}`,
    `L${dlx},${dly}`,
    `L${x3},${y4}`,
    `L${x1},${y4}`,
    `L${x1},${y3}`,
    `L${x4},${y3}`,
    `L${x5},${y2}`,
    `L${x1},${y2}`,
    'Z',
  ].join(' ')
})

presetShapes.set('round1Rect', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 16667)
  const r = Math.min(w, h) * a
  return [`M0,0`, `L${w - r},0`, `A${r},${r} 0 0,1 ${w},${r}`, `L${w},${h}`, `L0,${h}`, 'Z'].join(
    ' '
  )
})

presetShapes.set('round2SameRect', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 16667)
  const a2 = adj(adjustments, 'adj2', 0)
  const r1 = Math.min(w, h) * a1
  const r2 = Math.min(w, h) * a2
  return [
    `M${r1},0`,
    `L${w - r1},0`,
    `A${r1},${r1} 0 0,1 ${w},${r1}`,
    `L${w},${h - r2}`,
    `A${r2},${r2} 0 0,1 ${w - r2},${h}`,
    `L${r2},${h}`,
    `A${r2},${r2} 0 0,1 0,${h - r2}`,
    `L0,${r1}`,
    `A${r1},${r1} 0 0,1 ${r1},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('round2DiagRect', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 16667)
  const a2 = adj(adjustments, 'adj2', 0)
  const r1 = Math.min(w, h) * a1
  const r2 = Math.min(w, h) * a2
  return [
    `M${r1},0`,
    `L${w},0`,
    `L${w},${h - r2}`,
    `A${r2},${r2} 0 0,1 ${w - r2},${h}`,
    `L0,${h}`,
    `L0,${r1}`,
    `A${r1},${r1} 0 0,1 ${r1},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('snip1Rect', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 16667)
  const d = Math.min(w, h) * a
  return `M0,0 L${w - d},0 L${w},${d} L${w},${h} L0,${h} Z`
})

presetShapes.set('snip2SameRect', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 16667)
  const a2 = adj(adjustments, 'adj2', 0)
  const d1 = Math.min(w, h) * a1
  const d2 = Math.min(w, h) * a2
  return `M${d1},0 L${w - d1},0 L${w},${d1} L${w},${h - d2} L${w - d2},${h} L${d2},${h} L0,${h - d2} L0,${d1} Z`
})

presetShapes.set('snip2DiagRect', (w, h, adjustments) => {
  // OOXML spec: diagonal snipped rectangle. adj1=top-left/bottom-right, adj2=top-right/bottom-left
  const ss = Math.min(w, h)
  const a1 = Math.min(Math.max(adjustments?.get('adj1') ?? 0, 0), 50000)
  const a2 = Math.min(Math.max(adjustments?.get('adj2') ?? 16667, 0), 50000)
  const lx1 = (ss * a1) / 100000
  const lx2 = w - lx1
  const ly1 = h - lx1
  const rx1 = (ss * a2) / 100000
  const rx2 = w - rx1
  const ry1 = h - rx1
  return `M${lx1},0 L${rx2},0 L${w},${rx1} L${w},${ly1} L${lx2},${h} L${rx1},${h} L0,${ry1} L0,${lx1} Z`
})

presetShapes.set('snipRoundRect', (w, h, adjustments) => {
  const a1 = adj(adjustments, 'adj1', 16667)
  const a2 = adj(adjustments, 'adj2', 16667)
  const r = Math.min(w, h) * a1
  const d = Math.min(w, h) * a2
  return [
    `M${r},0`,
    `L${w - d},0`,
    `L${w},${d}`,
    `L${w},${h}`,
    `L0,${h}`,
    `L0,${r}`,
    `A${r},${r} 0 0,1 ${r},0`,
    'Z',
  ].join(' ')
})

presetShapes.set('bevel', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 12500)
  const t = Math.min(w, h) * a
  return [
    // Outer
    `M0,0 L${w},0 L${w},${h} L0,${h} Z`,
    // Inner
    `M${t},${t} L${t},${h - t} L${w - t},${h - t} L${w - t},${t} Z`,
    // Connecting triangles (top)
    `M0,0 L${w},0 L${w - t},${t} L${t},${t} Z`,
    // Right
    `M${w},0 L${w},${h} L${w - t},${h - t} L${w - t},${t} Z`,
    // Bottom
    `M${w},${h} L0,${h} L${t},${h - t} L${w - t},${h - t} Z`,
    // Left
    `M0,${h} L0,0 L${t},${t} L${t},${h - t} Z`,
  ].join(' ')
})

presetShapes.set('foldedCorner', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 16667)
  const fold = Math.min(w, h) * a * 0.7
  return [
    `M0,0 L${w},0 L${w},${h} L0,${h} Z`,
    // Fold triangle
    `M${w - fold},${h} L${w},${h} L${w},${h - fold}`,
  ].join(' ')
})

// smileyFace is implemented as multiPathPreset (see multiPathPresets below)

presetShapes.set('sun', (w, h, adjustments) => {
  // OOXML spec: adj default=25000, pinned 12500..46875
  const adjRaw = adjustments?.get('adj') ?? 25000
  const a = Math.min(Math.max(adjRaw, 12500), 46875)
  const g0 = 50000 - a
  // OOXML guide formulas
  const g1 = (g0 * 30274) / 32768
  const g2 = (g0 * 12540) / 32768
  const _g3 = g1 + 50000
  const _g4 = g2 + 50000
  const g5 = 50000 - g1
  const g6 = 50000 - g2
  const g7 = (g0 * 23170) / 32768
  const g8 = 50000 + g7
  const g9 = 50000 - g7
  const g10 = (g5 * 3) / 4
  const g11 = (g6 * 3) / 4
  const g12 = g10 + 3662
  const g13 = g11 + 3662
  const g14 = g11 + 12500
  const g15 = 100000 - g10
  const g16 = 100000 - g12
  const g17 = 100000 - g13
  const g18 = 100000 - g14
  // Pixel coordinates
  const hc = w / 2
  const vc = h / 2
  const ox1 = (w * 18436) / 21600
  const oy1 = (h * 3163) / 21600
  const ox2 = (w * 3163) / 21600
  const oy2 = (h * 18436) / 21600
  const s = (pct: number, dim: number) => (dim * pct) / 100000
  const _x8 = s(g8, w)
  const _x9 = s(g9, w)
  const x10 = s(g10, w)
  const x12 = s(g12, w)
  const x13 = s(g13, w)
  const x14 = s(g14, w)
  const x15 = s(g15, w)
  const x16 = s(g16, w)
  const x17 = s(g17, w)
  const x18 = s(g18, w)
  const wR = s(g0, w)
  const hR = s(g0, h)
  const _y8 = s(g8, h)
  const _y9 = s(g9, h)
  const y10 = s(g10, h)
  const y12 = s(g12, h)
  const y13 = s(g13, h)
  const y14 = s(g14, h)
  const y15 = s(g15, h)
  const y16 = s(g16, h)
  const y17 = s(g17, h)
  const y18 = s(g18, h)
  const x19 = s(a, w)
  return [
    // Ray 0: right
    `M${w},${vc} L${x15},${y18} L${x15},${y14} Z`,
    // Ray 1: top-right
    `M${ox1},${oy1} L${x16},${y13} L${x17},${y12} Z`,
    // Ray 2: top
    `M${hc},0 L${x18},${y10} L${x14},${y10} Z`,
    // Ray 3: top-left
    `M${ox2},${oy1} L${x13},${y12} L${x12},${y13} Z`,
    // Ray 4: left
    `M0,${vc} L${x10},${y14} L${x10},${y18} Z`,
    // Ray 5: bottom-left
    `M${ox2},${oy2} L${x12},${y17} L${x13},${y16} Z`,
    // Ray 6: bottom
    `M${hc},${h} L${x14},${y15} L${x18},${y15} Z`,
    // Ray 7: bottom-right
    `M${ox1},${oy2} L${x17},${y16} L${x16},${y17} Z`,
    // Center ellipse (arcTo from x19,vc with wR,hR, startAngle=180°, sweep=360°)
    `M${x19},${vc}`,
    `A${wR},${hR} 0 1,1 ${x19 + 2 * wR},${vc}`,
    `A${wR},${hR} 0 1,1 ${x19},${vc}`,
    'Z',
  ].join(' ')
})

presetShapes.set('moon', (w, h, adjustments) => {
  if (w <= 0 || h <= 0) return `M0,0 L${w},0 L${w},${h} L0,${h} Z`
  // OOXML moon: outer semicircle (rx=w, ry=h/2) + inner semicircle (rx=g18w, ry=dy1).
  // Both arcs share endpoints (w,0) and (w,h). Inner ellipse centered at (g0w+g18w, h/2).
  const ss = Math.min(w, h)
  const hd2 = h / 2
  const a = Math.min(Math.max(adjustments?.get('adj') ?? 50000, 0), 87500)
  const g0 = (ss * a) / 100000
  const g1 = ss - g0
  if (g1 <= 0) return `M0,0 L${w},0 L${w},${h} L0,${h} Z`
  const g0w = (g0 * w) / ss
  const g5 = (2 * ss * ss - g0 * g0) / g1
  const g6w = ((g5 - g0) * w) / ss
  const g8 = g5 / 2 - g0
  const dy1 = (g8 * hd2) / ss
  const g18w = (g6w - g0w) / 2
  return [
    `M${w},${h}`,
    `A${w},${hd2} 0 0,1 ${w},0`, // outer: (w,h) → left semicircle → (w,0)
    `A${g18w},${dy1} 0 0,0 ${w},${h}`, // inner: (w,0) → concave arc → (w,h)
    'Z',
  ].join(' ')
})

presetShapes.set('lightningBolt', (w, h) => {
  // Calibrated against OOXML preset rendering (PowerPoint PDF export):
  // the old simplified 7-point bolt was too wide and lacked the inner notches.
  // This normalized 11-point contour follows the default lightningBolt geometry.
  return [
    `M${w * 0.3895},${h * 0.0}`,
    `L${w * 0.0},${h * 0.1821}`,
    `L${w * 0.3425},${h * 0.3845}`,
    `L${w * 0.2265},${h * 0.4452}`,
    `L${w * 0.5497},${h * 0.6391}`,
    `L${w * 0.453},${h * 0.683}`,
    `L${w * 0.9972},${h * 0.9983}`,
    `L${w * 0.6796},${h * 0.5919}`,
    `L${w * 0.7624},${h * 0.5514}`,
    `L${w * 0.5138},${h * 0.3153}`,
    `L${w * 0.5939},${h * 0.2816}`,
    'Z',
  ].join(' ')
})

presetShapes.set('bracketPair', (w, h, adjustments) => {
  // OOXML: adj=16667 (max 50000), radius = ss * a / 100000
  const ss = Math.min(w, h)
  const a = Math.min(Math.max(adjRaw(adjustments, 'adj', 16667), 0), 50000)
  const r = (ss * a) / 100000
  const x2 = w - r
  const y2 = h - r
  return [
    // Left bracket: bottom-left arc → vertical → top-left arc
    `M${r},${h}`,
    `A${r},${r} 0 0,1 0,${y2}`,
    `L0,${r}`,
    `A${r},${r} 0 0,1 ${r},0`,
    // Right bracket: top-right arc → vertical → bottom-right arc
    `M${x2},0`,
    `A${r},${r} 0 0,1 ${w},${r}`,
    `L${w},${y2}`,
    `A${r},${r} 0 0,1 ${x2},${h}`,
  ].join(' ')
})

presetShapes.set('bracePair', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 8333)
  const r = Math.min(w, h) * a
  const cy = h / 2
  return [
    // Left brace
    `M${r * 2},0`,
    `A${r},${r} 0 0,0 ${r},${r}`,
    `L${r},${cy - r}`,
    `A${r},${r} 0 0,1 0,${cy}`,
    `A${r},${r} 0 0,1 ${r},${cy + r}`,
    `L${r},${h - r}`,
    `A${r},${r} 0 0,0 ${r * 2},${h}`,
    // Right brace
    `M${w - r * 2},0`,
    `A${r},${r} 0 0,1 ${w - r},${r}`,
    `L${w - r},${cy - r}`,
    `A${r},${r} 0 0,0 ${w},${cy}`,
    `A${r},${r} 0 0,0 ${w - r},${cy + r}`,
    `L${w - r},${h - r}`,
    `A${r},${r} 0 0,1 ${w - r * 2},${h}`,
  ].join(' ')
})

presetShapes.set('leftBracket', (w, h, adjustments) => {
  const ss = Math.min(w, h)
  const maxAdj = ss > 0 ? (50000 * h) / ss : 0
  const a = Math.max(0, Math.min(adjustments?.get('adj') ?? 8333, maxAdj))
  const y1 = (ss * a) / 100000
  const toDeg = (ooxmlAng: number) => ooxmlAng / 60000
  const arcFrom = (
    x0: number,
    y0: number,
    rx: number,
    ry: number,
    stAng: number,
    swAng: number
  ) => {
    const st = (toDeg(stAng) * Math.PI) / 180
    const sw = (toDeg(swAng) * Math.PI) / 180
    const cx = x0 - rx * Math.cos(st)
    const cy = y0 - ry * Math.sin(st)
    const x1 = cx + rx * Math.cos(st + sw)
    const y1p = cy + ry * Math.sin(st + sw)
    const large = Math.abs(toDeg(swAng)) > 180 ? 1 : 0
    const sweep = swAng >= 0 ? 1 : 0
    return { cmd: `A${rx},${ry} 0 ${large},${sweep} ${x1},${y1p}`, x: x1, y: y1p }
  }

  const a1 = arcFrom(w, h, w, y1, 5400000, 5400000) // cd4, cd4
  const a2 = arcFrom(0, y1, w, y1, 10800000, 5400000) // cd2, cd4
  return [`M${w},${h}`, a1.cmd, `L0,${y1}`, a2.cmd].join(' ')
})

presetShapes.set('rightBracket', (w, h, adjustments) => {
  const ss = Math.min(w, h)
  const maxAdj = ss > 0 ? (50000 * h) / ss : 0
  const a = Math.max(0, Math.min(adjustments?.get('adj') ?? 8333, maxAdj))
  const y1 = (ss * a) / 100000
  const y2 = h - y1
  const toDeg = (ooxmlAng: number) => ooxmlAng / 60000
  const arcFrom = (
    x0: number,
    y0: number,
    rx: number,
    ry: number,
    stAng: number,
    swAng: number
  ) => {
    const st = (toDeg(stAng) * Math.PI) / 180
    const sw = (toDeg(swAng) * Math.PI) / 180
    const cx = x0 - rx * Math.cos(st)
    const cy = y0 - ry * Math.sin(st)
    const x1 = cx + rx * Math.cos(st + sw)
    const y1p = cy + ry * Math.sin(st + sw)
    const large = Math.abs(toDeg(swAng)) > 180 ? 1 : 0
    const sweep = swAng >= 0 ? 1 : 0
    return { cmd: `A${rx},${ry} 0 ${large},${sweep} ${x1},${y1p}`, x: x1, y: y1p }
  }

  const a1 = arcFrom(0, 0, w, y1, 16200000, 5400000) // 3cd4, cd4
  const a2 = arcFrom(w, y2, w, y1, 0, 5400000) // 0, cd4
  return [`M0,0`, a1.cmd, `L${w},${y2}`, a2.cmd].join(' ')
})

presetShapes.set('leftBrace', (w, h, adjustments) => {
  const ss = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 50000, 100000))
  const q1 = 100000 - a2
  const q2 = Math.min(q1, a2)
  const q3 = q2 / 2
  const maxAdj1 = ss > 0 ? (q3 * h) / ss : 0
  const a1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 8333, maxAdj1))
  const y1 = (ss * a1) / 100000
  const y3 = (h * a2) / 100000
  const y4 = y3 + y1
  const wd2 = w / 2
  const hc = w / 2
  const toDeg = (ooxmlAng: number) => ooxmlAng / 60000
  const arcFrom = (
    x0: number,
    y0: number,
    rx: number,
    ry: number,
    stAng: number,
    swAng: number
  ) => {
    const st = (toDeg(stAng) * Math.PI) / 180
    const sw = (toDeg(swAng) * Math.PI) / 180
    const cx = x0 - rx * Math.cos(st)
    const cy = y0 - ry * Math.sin(st)
    const x1 = cx + rx * Math.cos(st + sw)
    const y1p = cy + ry * Math.sin(st + sw)
    const large = Math.abs(toDeg(swAng)) > 180 ? 1 : 0
    const sweep = swAng >= 0 ? 1 : 0
    return { cmd: `A${rx},${ry} 0 ${large},${sweep} ${x1},${y1p}`, x: x1, y: y1p }
  }

  let x = w
  let y = h
  const aTop = arcFrom(x, y, wd2, y1, 5400000, 5400000) // cd4, cd4
  x = aTop.x
  y = aTop.y
  const aMid1 = arcFrom(hc, y4, wd2, y1, 0, -5400000)
  const aMid2 = arcFrom(aMid1.x, aMid1.y, wd2, y1, 5400000, -5400000)
  const aBot = arcFrom(hc, y1, wd2, y1, 10800000, 5400000) // cd2, cd4
  return [
    `M${w},${h}`,
    aTop.cmd,
    `L${hc},${y4}`,
    aMid1.cmd,
    aMid2.cmd,
    `L${hc},${y1}`,
    aBot.cmd,
  ].join(' ')
})

presetShapes.set('rightBrace', (w, h, adjustments) => {
  const ss = Math.min(w, h)
  const a2 = Math.max(0, Math.min(adjustments?.get('adj2') ?? 50000, 100000))
  const q1 = 100000 - a2
  const q2 = Math.min(q1, a2)
  const q3 = q2 / 2
  const maxAdj1 = ss > 0 ? (q3 * h) / ss : 0
  const a1 = Math.max(0, Math.min(adjustments?.get('adj1') ?? 8333, maxAdj1))
  const y1 = (ss * a1) / 100000
  const y3 = (h * a2) / 100000
  const y2 = y3 - y1
  const y4 = h - y1
  const wd2 = w / 2
  const hc = w / 2
  const toDeg = (ooxmlAng: number) => ooxmlAng / 60000
  const arcFrom = (
    x0: number,
    y0: number,
    rx: number,
    ry: number,
    stAng: number,
    swAng: number
  ) => {
    const st = (toDeg(stAng) * Math.PI) / 180
    const sw = (toDeg(swAng) * Math.PI) / 180
    const cx = x0 - rx * Math.cos(st)
    const cy = y0 - ry * Math.sin(st)
    const x1 = cx + rx * Math.cos(st + sw)
    const y1p = cy + ry * Math.sin(st + sw)
    const large = Math.abs(toDeg(swAng)) > 180 ? 1 : 0
    const sweep = swAng >= 0 ? 1 : 0
    return { cmd: `A${rx},${ry} 0 ${large},${sweep} ${x1},${y1p}`, x: x1, y: y1p }
  }

  const aTop = arcFrom(0, 0, wd2, y1, 16200000, 5400000) // 3cd4, cd4
  const aMid1 = arcFrom(hc, y2, wd2, y1, 10800000, -5400000) // cd2,-cd4
  const aMid2 = arcFrom(aMid1.x, aMid1.y, wd2, y1, 16200000, -5400000) //3cd4,-cd4
  const aBot = arcFrom(hc, y4, wd2, y1, 0, 5400000) //0,cd4
  return [`M0,0`, aTop.cmd, `L${hc},${y2}`, aMid1.cmd, aMid2.cmd, `L${hc},${y4}`, aBot.cmd].join(
    ' '
  )
})

// ===== Action Buttons =====
// Action buttons are multi-path shapes: background rect + icon with darken fill + icon outline + rect outline.
// OOXML spec uses ss*3/8 as the icon half-size (dx2), with the icon centred at (hc, vc).
// Shapes with multiPathPresets entries below get proper 3D treatment. Remaining shapes
// fall back to the legacy actionButtonIcons overlay (single flat icon path).

presetShapes.set('actionButtonBlank', (w, h) => `M0,0 L${w},0 L${w},${h} L0,${h} Z`)

// Fallback rectangle for action buttons without multiPathPresets entry yet
// actionButtonSound fallback removed — uses multiPathPresets entry below

// Multi-path action button presets are registered after the multiPathPresets Map
// declaration (see below in the multiPathPresets section).

// ---------------------------------------------------------------------------
// Action button icon paths (rendered as a second <path> with contrasting fill)
// ---------------------------------------------------------------------------
const actionButtonIcons = new Map<string, (w: number, h: number) => string>()

// actionButtonHome icon removed — uses multiPathPresets entry below

actionButtonIcons.set('actionButtonForwardNext', (w, h) => {
  // Right-pointing triangle (▶)
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.3
  return `M${cx - s * 0.5},${cy - s} L${cx + s},${cy} L${cx - s * 0.5},${cy + s} Z`
})

actionButtonIcons.set('actionButtonBackPrevious', (w, h) => {
  // Left-pointing triangle (◀)
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.3
  return `M${cx + s * 0.5},${cy - s} L${cx - s},${cy} L${cx + s * 0.5},${cy + s} Z`
})

actionButtonIcons.set('actionButtonReturn', (w, h) => {
  // Curved return arrow (↩) — shaft goes right at bottom, curves UP at right end,
  // returns left at top with arrowhead pointing left (standard PowerPoint icon).
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.28
  const thick = s * 0.22 // shaft thickness
  const bottomY = cy + s * 0.4
  const topY = cy - s * 0.4
  const leftX = cx - s * 0.6
  const rightX = cx + s * 0.6
  const r = (bottomY - topY) / 2 // semicircle radius
  return [
    // Outer edge: bottom-left → right → arc up → left to arrowhead junction
    `M${leftX},${bottomY}`,
    `L${rightX},${bottomY}`,
    `A${r},${r} 0 0,1 ${rightX},${topY}`,
    `L${leftX + s * 0.15},${topY}`,
    // Inner edge: top → right → arc down → bottom-left
    `L${leftX + s * 0.15},${topY + thick}`,
    `L${rightX - thick * 0.3},${topY + thick}`,
    `A${r - thick},${r - thick} 0 0,0 ${rightX - thick * 0.3},${bottomY - thick}`,
    `L${leftX},${bottomY - thick}`,
    `Z`,
    // Arrowhead pointing left at top-left
    `M${leftX - s * 0.3},${topY + thick / 2}`,
    `L${leftX + s * 0.15},${topY - s * 0.2}`,
    `L${leftX + s * 0.15},${topY + thick + s * 0.2}`,
    `Z`,
  ].join(' ')
})

actionButtonIcons.set('actionButtonBeginning', (w, h) => {
  // Skip-to-beginning (|◀)
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.28
  return [
    // Left bar
    `M${cx - s},${cy - s} L${cx - s + s * 0.2},${cy - s} L${cx - s + s * 0.2},${cy + s} L${cx - s},${cy + s} Z`,
    // Left-pointing triangle
    `M${cx + s},${cy - s} L${cx - s + s * 0.35},${cy} L${cx + s},${cy + s} Z`,
  ].join(' ')
})

actionButtonIcons.set('actionButtonEnd', (w, h) => {
  // Skip-to-end (▶|)
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.28
  return [
    // Right bar
    `M${cx + s - s * 0.2},${cy - s} L${cx + s},${cy - s} L${cx + s},${cy + s} L${cx + s - s * 0.2},${cy + s} Z`,
    // Right-pointing triangle
    `M${cx - s},${cy - s} L${cx + s - s * 0.35},${cy} L${cx - s},${cy + s} Z`,
  ].join(' ')
})

// actionButtonHelp icon removed — uses multiPathPresets entry below

actionButtonIcons.set('actionButtonInformation', (w, h) => {
  // Info icon (i)
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.28
  return [
    // Dot
    `M${cx - s * 0.1},${cy - s * 0.65} L${cx + s * 0.1},${cy - s * 0.65} L${cx + s * 0.1},${cy - s * 0.4} L${cx - s * 0.1},${cy - s * 0.4} Z`,
    // Stem
    `M${cx - s * 0.12},${cy - s * 0.2} L${cx + s * 0.12},${cy - s * 0.2} L${cx + s * 0.12},${cy + s * 0.65} L${cx - s * 0.12},${cy + s * 0.65} Z`,
  ].join(' ')
})

actionButtonIcons.set('actionButtonDocument', (w, h) => {
  // Document with folded corner
  const cx = w / 2
  const cy = h / 2
  const s = Math.min(w, h) * 0.28
  const dx = s * 0.7
  const dy = s
  const fold = s * 0.3
  return [
    `M${cx - dx},${cy - dy}`,
    `L${cx + dx - fold},${cy - dy} L${cx + dx},${cy - dy + fold}`,
    `L${cx + dx},${cy + dy} L${cx - dx},${cy + dy} Z`,
    `M${cx + dx - fold},${cy - dy} L${cx + dx - fold},${cy - dy + fold} L${cx + dx},${cy - dy + fold}`,
  ].join(' ')
})

// actionButtonSound icon removed — uses multiPathPresets entry below

// actionButtonMovie icon is now rendered via multiPathPresets (see below).

/**
 * Get the SVG path for the icon overlay of an action button.
 * Returns undefined if the shape is not an action button or is actionButtonBlank.
 */
export function getActionButtonIconPath(
  shapeType: string,
  w: number,
  h: number
): string | undefined {
  const key = shapeType.toLowerCase()
  const generator = actionButtonIcons.get(key) ?? actionButtonIcons.get(shapeType)
  return generator?.(w, h)
}

// ===== Aliases and common alternative names =====

// Some shapes are known by multiple names in different OOXML versions
// flowChartOfflineStorage: registered as multiPathPreset (see below)

// ribbon is implemented as multiPathPreset (see multiPathPresets below)

presetShapes.set('wave', (w, h, adjustments) => {
  // OOXML: adj1=12500 (max 20000), adj2=0 (phase shift, range -10000..10000)
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj1', 12500), 0), 20000)
  const a2 = Math.min(Math.max(adjRaw(adjustments, 'adj2', 0), -10000), 10000)
  const y1 = (h * a1) / 100000
  const dy2 = (y1 * 10) / 3
  const y2 = y1 - dy2 // control above crest
  const y3 = y1 + dy2 // control below crest
  const y4 = h - y1 // bottom wave y
  const y5 = y4 - dy2
  const y6 = y4 + dy2
  // Phase shift
  const of2 = (w * a2) / 50000
  const dx2 = of2 < 0 ? 0 : of2
  const dx5 = of2 < 0 ? of2 : 0
  const x2 = -dx2
  const x5 = w - dx5
  const dx3 = (x5 - x2) / 3
  const x3 = x2 + dx3
  const x4 = (x3 + x5) / 2
  const x6 = dx5
  const x10 = w + dx2
  const x7 = x6 + (x10 - x6) / 3
  const x8 = (x7 + x10) / 2
  return [
    `M${x2},${y1}`,
    `C${x3},${y2} ${x4},${y3} ${x5},${y1}`,
    `L${x10},${y4}`,
    `C${x8},${y6} ${x7},${y5} ${x6},${y4}`,
    'Z',
  ].join(' ')
})

presetShapes.set('doubleWave', (w, h, adjustments) => {
  // OOXML: adj1=6250 (max 12500), adj2=0 (phase shift)
  const a1 = Math.min(Math.max(adjRaw(adjustments, 'adj1', 6250), 0), 12500)
  const a2 = Math.min(Math.max(adjRaw(adjustments, 'adj2', 0), -10000), 10000)
  const y1 = (h * a1) / 100000
  const dy2 = (y1 * 10) / 3
  const y2 = y1 - dy2
  const y3 = y1 + dy2
  const y4 = h - y1
  const y5 = y4 - dy2
  const y6 = y4 + dy2
  const of2 = (w * a2) / 50000
  const dx2 = of2 < 0 ? 0 : of2
  const dx8 = of2 < 0 ? of2 : 0
  const x2 = -dx2
  const x8 = w - dx8
  const dx3 = (x8 - x2) / 6
  const x3 = x2 + dx3
  const dx4 = (x8 - x2) / 3
  const x4 = x2 + dx4
  const x5 = (x2 + x8) / 2
  const x6 = x5 + dx3
  const x7 = (x6 + x8) / 2
  const x9 = dx8
  const x15 = w + dx2
  const dx3b = (x15 - x9) / 6
  const x10 = x9 + dx3b
  const x11 = x9 + (x15 - x9) / 3
  const x12 = (x9 + x15) / 2
  const x13 = x12 + dx3b
  const x14 = (x13 + x15) / 2
  return [
    `M${x2},${y1}`,
    `C${x3},${y2} ${x4},${y3} ${x5},${y1}`,
    `C${x6},${y2} ${x7},${y3} ${x8},${y1}`,
    `L${x15},${y4}`,
    `C${x14},${y6} ${x13},${y5} ${x12},${y4}`,
    `C${x11},${y6} ${x10},${y5} ${x9},${y4}`,
    'Z',
  ].join(' ')
})

// verticalScroll and horizontalScroll are implemented as multi-path presets
// (see multiPathPresets below) for accurate OOXML rendering with darkenLess shadows.

presetShapes.set('irregularSeal1', (w, h) => {
  // OOXML spec: exact coordinates on 21600x21600 grid
  const sx = (x: number) => (w * x) / 21600
  const sy = (y: number) => (h * y) / 21600
  return [
    `M${sx(10800)},${sy(5800)}`,
    `L${sx(14522)},0`,
    `L${sx(14155)},${sy(5325)}`,
    `L${sx(18380)},${sy(4457)}`,
    `L${sx(16702)},${sy(7315)}`,
    `L${sx(21097)},${sy(8137)}`,
    `L${sx(17607)},${sy(10475)}`,
    `L${sx(21600)},${sy(13290)}`,
    `L${sx(16837)},${sy(12942)}`,
    `L${sx(18145)},${sy(18095)}`,
    `L${sx(14020)},${sy(14457)}`,
    `L${sx(13247)},${sy(19737)}`,
    `L${sx(10532)},${sy(14935)}`,
    `L${sx(8485)},${sy(21600)}`,
    `L${sx(7715)},${sy(15627)}`,
    `L${sx(4762)},${sy(17617)}`,
    `L${sx(5667)},${sy(13937)}`,
    `L${sx(135)},${sy(14587)}`,
    `L${sx(3722)},${sy(11775)}`,
    `L0,${sy(8615)}`,
    `L${sx(4627)},${sy(7617)}`,
    `L${sx(370)},${sy(2295)}`,
    `L${sx(7312)},${sy(6320)}`,
    `L${sx(8352)},${sy(2295)}`,
    'Z',
  ].join(' ')
})

presetShapes.set('irregularSeal2', (w, h) => {
  // Office-like irregularSeal2 coordinates (21600 design grid).
  return [
    `M${(w * 11462) / 21600},${(h * 4342) / 21600}`,
    `L${(w * 14790) / 21600},0`,
    `L${(w * 14525) / 21600},${(h * 5777) / 21600}`,
    `L${(w * 18007) / 21600},${(h * 3172) / 21600}`,
    `L${(w * 16380) / 21600},${(h * 6532) / 21600}`,
    `L${w},${(h * 6645) / 21600}`,
    `L${(w * 16985) / 21600},${(h * 9402) / 21600}`,
    `L${(w * 18270) / 21600},${(h * 11290) / 21600}`,
    `L${(w * 16380) / 21600},${(h * 12310) / 21600}`,
    `L${(w * 18877) / 21600},${(h * 15632) / 21600}`,
    `L${(w * 14640) / 21600},${(h * 14350) / 21600}`,
    `L${(w * 14942) / 21600},${(h * 17370) / 21600}`,
    `L${(w * 12180) / 21600},${(h * 15935) / 21600}`,
    `L${(w * 11612) / 21600},${(h * 18842) / 21600}`,
    `L${(w * 9872) / 21600},${(h * 17370) / 21600}`,
    `L${(w * 8700) / 21600},${(h * 19712) / 21600}`,
    `L${(w * 7527) / 21600},${(h * 18125) / 21600}`,
    `L${(w * 4917) / 21600},${h}`,
    `L${(w * 4805) / 21600},${(h * 18240) / 21600}`,
    `L${(w * 1285) / 21600},${(h * 17825) / 21600}`,
    `L${(w * 3330) / 21600},${(h * 15370) / 21600}`,
    `L0,${(h * 12877) / 21600}`,
    `L${(w * 3935) / 21600},${(h * 11592) / 21600}`,
    `L${(w * 1172) / 21600},${(h * 8270) / 21600}`,
    `L${(w * 5372) / 21600},${(h * 7817) / 21600}`,
    `L${(w * 4502) / 21600},${(h * 3625) / 21600}`,
    `L${(w * 8550) / 21600},${(h * 6382) / 21600}`,
    `L${(w * 9722) / 21600},${(h * 1887) / 21600}`,
    'Z',
  ].join(' ')
})

presetShapes.set('teardrop', (w, h) => {
  const rx = w / 2
  const ry = h / 2
  return [`M${w},${ry}`, `A${rx},${ry} 0 1,1 ${rx},0`, `L${w},0`, `L${w},${ry}`, 'Z'].join(' ')
})

presetShapes.set('pie', (w, h, adjustments) => {
  // OOXML pie: adj1 = start angle, adj2 = end angle (60000ths of a degree). Sweep clockwise from start to end.
  // OOXML angles are "visual" (geometric) — must convert to parametric for ellipses (rx≠ry).
  const adj1Raw = adjustments?.get('adj1') ?? 0
  const adj2Raw = adjustments?.get('adj2') ?? 16200000 // 270° end default
  const startDeg = (adj1Raw / 60000) % 360
  const endDeg = (adj2Raw / 60000) % 360
  let sweepDeg = (((endDeg - startDeg) % 360) + 360) % 360
  if (sweepDeg === 0 && startDeg !== endDeg) sweepDeg = 360
  const rx = w / 2
  const ry = h / 2
  const toRad = (d: number) => (d * Math.PI) / 180
  const visualToParam = (deg: number) =>
    Math.atan2(Math.sin(toRad(deg)) / ry, Math.cos(toRad(deg)) / rx)
  const startParam = visualToParam(startDeg)
  const endParam = visualToParam(endDeg)
  const x1 = rx + rx * Math.cos(startParam)
  const y1 = ry + ry * Math.sin(startParam)
  const x2 = rx + rx * Math.cos(endParam)
  const y2 = ry + ry * Math.sin(endParam)
  const largeArc = sweepDeg > 180 ? 1 : 0
  return [`M${rx},${ry}`, `L${x1},${y1}`, `A${rx},${ry} 0 ${largeArc},1 ${x2},${y2}`, 'Z'].join(' ')
})

presetShapes.set('pieWedge', (w, h) => {
  // OOXML: Quarter-ellipse pie wedge. Center at (w, h), radii = (w, h).
  // Arc from 180° sweeping 90° CW: starts at (0, h), ends at (w, 0).
  // The arc bulges toward the upper-left.
  return [`M0,${h}`, `A${w},${h} 0 0,1 ${w},0`, `L${w},${h}`, 'Z'].join(' ')
})

presetShapes.set('arc', (w, h, adjustments) => {
  // OOXML arc: adj1/adj2 are angles in 60000ths of a degree
  // OOXML angles are "visual" (geometric) — must convert to parametric for ellipses (rx≠ry).
  const adj1Raw = adjustments?.get('adj1') ?? 16200000 // default 270°
  const adj2Raw = adjustments?.get('adj2') ?? 0 // default 0°
  const startDeg = adj1Raw / 60000
  const endDeg = adj2Raw / 60000
  const rx = w / 2
  const ry = h / 2
  const toRad = (d: number) => (d * Math.PI) / 180
  const visualToParam = (deg: number) =>
    Math.atan2(Math.sin(toRad(deg)) / ry, Math.cos(toRad(deg)) / rx)
  const startParam = visualToParam(startDeg)
  const endParam = visualToParam(endDeg)
  const x1 = rx + rx * Math.cos(startParam)
  const y1 = ry + ry * Math.sin(startParam)
  const x2 = rx + rx * Math.cos(endParam)
  const y2 = ry + ry * Math.sin(endParam)
  let sweepDeg = (((endDeg - startDeg) % 360) + 360) % 360
  if (sweepDeg === 0 && startDeg !== endDeg) sweepDeg = 360
  const largeArc = sweepDeg > 180 ? 1 : 0
  return `M${x1},${y1} A${rx},${ry} 0 ${largeArc},1 ${x2},${y2}`
})

presetShapes.set('chord', (w, h, adjustments) => {
  // OOXML chord: arc + chord line. Spec uses ellipse (arcTo wR="wd2" hR="hd2") per presetShapeDefinitions.
  // OOXML angles are "visual" (geometric) angles — the angle of the ray from center to the point.
  // For ellipses (rx≠ry), convert to parametric angle: t = atan2(sin(θ)/ry, cos(θ)/rx)
  const adj1Raw = adjustments?.get('adj1') ?? 2700000 // default 45°
  const adj2Raw = adjustments?.get('adj2') ?? 16200000 // default 270°
  const startDeg = adj1Raw / 60000
  const endDeg = adj2Raw / 60000
  const cx = w / 2
  const cy = h / 2
  const rx = w / 2
  const ry = h / 2
  const toRad = (d: number) => (d * Math.PI) / 180
  // Convert OOXML visual angles to parametric angles on the ellipse
  const visualToParam = (deg: number) =>
    Math.atan2(Math.sin(toRad(deg)) / ry, Math.cos(toRad(deg)) / rx)
  const startParam = visualToParam(startDeg)
  const endParam = visualToParam(endDeg)
  const x1 = cx + rx * Math.cos(startParam)
  const y1 = cy + ry * Math.sin(startParam)
  const x2 = cx + rx * Math.cos(endParam)
  const y2 = cy + ry * Math.sin(endParam)
  // Use OOXML visual sweep to determine large-arc-flag
  let sweepDeg = (((endDeg - startDeg) % 360) + 360) % 360
  if (sweepDeg === 0 && startDeg !== endDeg) sweepDeg = 360
  // When adj1 == adj2, the chord covers the full ellipse (360° sweep)
  if (sweepDeg === 0) {
    return `M${cx - rx},${cy} A${rx},${ry} 0 1,1 ${cx + rx},${cy} A${rx},${ry} 0 1,1 ${cx - rx},${cy} Z`
  }
  const largeArc = sweepDeg > 180 ? 1 : 0
  return `M${x1},${y1} A${rx},${ry} 0 ${largeArc},1 ${x2},${y2} Z`
})

presetShapes.set('funnel', (w, h) => {
  // OOXML funnel: top rim ellipse arc + tapered sides + bottom spout arc + inset top ellipse.
  // From presetShapeDefinitions.xml (ECMA-376).
  const ss = Math.min(w, h)
  const wd2 = w / 2
  const hd4 = h / 4
  const hc = w / 2
  const b = h

  const d = ss / 20 // inset margin
  const rw2 = wd2 - d // inset top-ellipse x-radius
  const rh2 = hd4 - d // inset top-ellipse y-radius

  // Angle (in radians) where funnel sides are tangent to top ellipse.
  // OOXML: t1 = cos(wd2, 480000), t2 = sin(hd4, 480000) → da = atan2(t1, t2)
  // 480000 = 8° in 60000ths of a degree
  const ang8 = (8 * Math.PI) / 180
  const t1 = wd2 * Math.cos(ang8)
  const t2 = hd4 * Math.sin(ang8)
  const da = Math.atan2(t2, t1) // radians

  // Angles for the top rim arc (OOXML convention: sweep from stAng1 by swAng1)
  const stAng1 = Math.PI - da // cd2 - da
  const swAng1 = Math.PI + 2 * da // cd2 + 2*da

  // Sweep for the bottom spout arc
  const swAng3 = Math.PI - 2 * da // cd2 - 2*da

  // Bottom spout ellipse radii: 1/4 of top ellipse
  const rw3 = wd2 / 4
  const rh3 = hd4 / 4

  // Start point on top ellipse at stAng1 (visual angle → ellipse point)
  // OOXML uses: n = (wR*hR) / mod(cos(hR,ang), sin(wR,ang), 0), then x = hc + cos(n,ang), y = hd4 + sin(n,ang)
  // This is equivalent to the parametric ellipse point at the "visual" angle.
  const ct1 = hd4 * Math.cos(stAng1)
  const st1 = wd2 * Math.sin(stAng1)
  const m1 = Math.sqrt(ct1 * ct1 + st1 * st1)
  const n1 = (wd2 * hd4) / m1
  const dx1 = n1 * Math.cos(stAng1)
  const dy1 = n1 * Math.sin(stAng1)
  const x1 = hc + dx1
  const y1 = hd4 + dy1

  // End point of top arc (at stAng1 + swAng1 = pi + da)
  const endAng1 = stAng1 + swAng1
  const ct1e = hd4 * Math.cos(endAng1)
  const st1e = wd2 * Math.sin(endAng1)
  const m1e = Math.sqrt(ct1e * ct1e + st1e * st1e)
  const n1e = (wd2 * hd4) / m1e
  const dx1e = n1e * Math.cos(endAng1)
  const dy1e = n1e * Math.sin(endAng1)
  const x1e = hc + dx1e
  const y1e = hd4 + dy1e

  // Point on spout ellipse at angle da
  const vc3 = b - rh3 // vertical center of spout ellipse
  const ct3 = rh3 * Math.cos(da)
  const st3 = rw3 * Math.sin(da)
  const m3 = Math.sqrt(ct3 * ct3 + st3 * st3)
  const n3 = (rw3 * rh3) / m3
  const dx3 = n3 * Math.cos(da)
  const dy3 = n3 * Math.sin(da)
  const x3 = hc + dx3
  const y2 = vc3 + dy3

  // End point of spout arc (at da + swAng3)
  const endAng3 = da + swAng3
  const ct3e = rh3 * Math.cos(endAng3)
  const st3e = rw3 * Math.sin(endAng3)
  const m3e = Math.sqrt(ct3e * ct3e + st3e * st3e)
  const n3e = (rw3 * rh3) / m3e
  const dx3e = n3e * Math.cos(endAng3)
  const dy3e = n3e * Math.sin(endAng3)
  const x3e = hc + dx3e
  const y2e = vc3 + dy3e

  // Determine arc flags
  const swDeg1 = (swAng1 * 180) / Math.PI
  const largeArc1 = Math.abs(swDeg1) > 180 ? 1 : 0
  const sweep1 = swAng1 > 0 ? 1 : 0

  const swDeg3 = (swAng3 * 180) / Math.PI
  const largeArc3 = Math.abs(swDeg3) > 180 ? 1 : 0
  const sweep3 = swAng3 > 0 ? 1 : 0

  // Sub-path 1: Funnel body (top arc → line to spout → spout arc → close)
  const body = [
    `M${x1},${y1}`,
    `A${wd2},${hd4} 0 ${largeArc1},${sweep1} ${x1e},${y1e}`,
    `L${x3},${y2}`,
    `A${rw3},${rh3} 0 ${largeArc3},${sweep3} ${x3e},${y2e}`,
    'Z',
  ].join(' ')

  // Sub-path 2: Inset top ellipse (full ellipse, counter-clockwise for even-odd hole)
  const x2 = wd2 - rw2 // leftmost point of inset ellipse
  const x2r = wd2 + rw2 // rightmost point
  const inset = [
    `M${x2},${hd4}`,
    `A${rw2},${rh2} 0 1,0 ${x2r},${hd4}`,
    `A${rw2},${rh2} 0 1,0 ${x2},${hd4}`,
    'Z',
  ].join(' ')

  return `${body} ${inset}`
})

// ===== Fallback =====

/**
 * Get the SVG path for a preset shape, falling back to a simple rectangle
 * if the shape type is not implemented.
 */
// ---------------------------------------------------------------------------
// Preset shape overlays — additional paths for 3D-like shapes (lighter top face, etc.)
// ---------------------------------------------------------------------------

interface PresetOverlay {
  /** SVG path d-attribute for the overlay */
  path: string
  /** Fill modifier: 'lighten' brightens the base fill */
  fillModifier: 'lighten'
}

export type PresetOverlayGenerator = (
  w: number,
  h: number,
  adjustments?: Map<string, number>
) => PresetOverlay[]

const presetOverlays: Map<string, PresetOverlayGenerator> = new Map()

presetOverlays.set('can', (w, h) => {
  const ry = h * 0.1
  const rx = w / 2
  return [
    {
      path: [`M0,${ry}`, `A${rx},${ry} 0 0,1 ${w},${ry}`, `A${rx},${ry} 0 0,1 0,${ry}`, 'Z'].join(
        ' '
      ),
      fillModifier: 'lighten',
    },
  ]
})

/**
 * Get overlay paths for a preset shape (3D top faces, etc.).
 * Returns empty array if the shape has no overlays.
 */
export function getPresetOverlays(
  shapeType: string,
  w: number,
  h: number,
  adjustments?: Map<string, number>
): PresetOverlay[] {
  const key = shapeType.toLowerCase()
  const gen = presetOverlays.get(key) ?? presetOverlays.get(shapeType)
  return gen ? gen(w, h, adjustments) : []
}

// ---------------------------------------------------------------------------
// Multi-path preset shapes — complex shapes with multiple SVG paths
// Each path has its own fill modifier and stroke behavior, matching OOXML spec.
// ---------------------------------------------------------------------------

/** A single sub-path within a multi-path preset shape. */
export interface PresetSubPath {
  /** SVG path d-attribute string */
  d: string
  /**
   * Fill behavior:
   * - 'norm': use the shape's normal fill
   * - 'darken': darken the base fill (multiply with ~60% gray)
   * - 'darkenLess': slightly darken (multiply with ~80% gray)
   * - 'lighten': lighten the base fill
   * - 'lightenLess': slightly lighten
   * - 'none': no fill (stroke-only detail lines)
   */
  fill: 'norm' | 'darken' | 'darkenLess' | 'lighten' | 'lightenLess' | 'none'
  /** Whether this path should have a stroke (default true) */
  stroke: boolean
  /** Optional stroke width multiplier for detail lines that should render lighter than the outline. */
  strokeWidthScale?: number
  /** Restrict visibility of this detail path to a stroke band around the main outline path. */
  maskToMainOutline?: boolean
  /** Optional scale for the outline-band mask stroke width. */
  maskStrokeScale?: number
  /** Restrict visibility of this detail path to the band between the main outline and an inset-scaled outline. */
  maskToMainOutlineBandScale?: number
}

type MultiPathPresetGenerator = (
  w: number,
  h: number,
  adjustments?: Map<string, number>
) => PresetSubPath[]

const multiPathPresets: Map<string, MultiPathPresetGenerator> = new Map()

// ===== Action Button multi-path presets (OOXML spec-accurate) =====
// Common helper: OOXML action button guide values
function _abGuides(w: number, h: number) {
  const ss = Math.min(w, h)
  const hc = w / 2
  const vc = h / 2
  const dx2 = (ss * 3) / 8 // icon half-extent
  return {
    ss,
    hc,
    vc,
    dx2,
    g9: vc - dx2,
    g10: vc + dx2,
    g11: hc - dx2,
    g12: hc + dx2,
    g13: (ss * 3) / 4,
  }
}
const _rect = (w: number, h: number) => `M0,0 L${w},0 L${w},${h} L0,${h} Z`

// actionButtonForwardNext (VBA 0130): right-pointing triangle ▶
multiPathPresets.set('actionButtonForwardNext', (w, h) => {
  const { g9, g10, g11, g12, vc } = _abGuides(w, h)
  const tri = `M${g12},${vc} L${g11},${g9} L${g11},${g10} Z`
  return [
    { d: `${_rect(w, h)} ${tri}`, fill: 'norm', stroke: false },
    { d: tri, fill: 'darken', stroke: false },
    { d: tri, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('actionButtonForward', (w, h) => {
  const forwardNext = multiPathPresets.get('actionButtonForwardNext')
  return forwardNext ? forwardNext(w, h) : []
})

// actionButtonBackPrevious (VBA 0129): left-pointing triangle ◀
multiPathPresets.set('actionButtonBackPrevious', (w, h) => {
  const { g9, g10, g11, g12, vc } = _abGuides(w, h)
  const tri = `M${g11},${vc} L${g12},${g9} L${g12},${g10} Z`
  return [
    { d: `${_rect(w, h)} ${tri}`, fill: 'norm', stroke: false },
    { d: tri, fill: 'darken', stroke: false },
    { d: tri, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonBeginning (VBA 0131): |◀ skip-to-start
multiPathPresets.set('actionButtonBeginning', (w, h) => {
  const { g9, g10, g11, g12, g13, vc } = _abGuides(w, h)
  const g14 = g13 / 8
  const g15 = g13 / 4
  const g16 = g11 + g14
  const g17 = g11 + g15
  const tri = `M${g17},${vc} L${g12},${g9} L${g12},${g10} Z`
  const bar = `M${g16},${g9} L${g11},${g9} L${g11},${g10} L${g16},${g10} Z`
  const icon = `${tri} ${bar}`
  return [
    { d: `${_rect(w, h)} ${icon}`, fill: 'norm', stroke: false },
    { d: icon, fill: 'darken', stroke: false },
    { d: icon, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonEnd (VBA 0132): ▶| skip-to-end
multiPathPresets.set('actionButtonEnd', (w, h) => {
  const { g9, g10, g11, g12, g13, vc } = _abGuides(w, h)
  const g14 = (g13 * 3) / 4
  const g15 = (g13 * 7) / 8
  const g16 = g11 + g14
  const g17 = g11 + g15
  const tri = `M${g16},${vc} L${g11},${g9} L${g11},${g10} Z`
  const bar = `M${g17},${g9} L${g12},${g9} L${g12},${g10} L${g17},${g10} Z`
  const icon = `${tri} ${bar}`
  return [
    { d: `${_rect(w, h)} ${icon}`, fill: 'norm', stroke: false },
    { d: icon, fill: 'darken', stroke: false },
    { d: icon, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonReturn (VBA 0133): curved return arrow ↩
// OOXML spec: 4 paths – bg+icon cutout (norm), icon fill (darken), icon outline (stroke), rect outline (stroke)
// Fill paths use inner arcs curving inward; outline path traces the full shape with reversed arc winding.
multiPathPresets.set('actionButtonReturn', (w, h) => {
  const { g9, g10, g11, g12, g13, hc, vc: _vcR } = _abGuides(w, h)
  const g14 = (g13 * 7) / 8
  const g15 = (g13 * 3) / 4
  const g16 = (g13 * 5) / 8
  const g17 = (g13 * 3) / 8 // outer arc radius
  const g18 = g13 / 4
  const g27 = g13 / 8 // inner arc radius
  const g19 = g9 + g15
  const g20 = g9 + g16
  const g21 = g9 + g18
  const g22 = g11 + g14
  const g23 = g11 + g15
  const g24 = g11 + g16
  const g25 = g11 + g17
  const g26 = g11 + g18

  // Fill icon path (paths 0 & 1 in OOXML spec — identical geometry)
  // Arc 1: from (g24, g20), wR=g27 hR=g27 stAng=0° swAng=90°
  //   center = (g24-g27, g20), endpoint = (g24-g27, g20+g27) = (g24-g27, g19)
  // Arc 2: from (g25, g19), wR=g27 hR=g27 stAng=90° swAng=90°
  //   center = (g25, g19-g27), endpoint = (g25-g27, g19-g27) = (g26, g20)
  // Arc 3: from (g11, g20), wR=g17 hR=g17 stAng=180° swAng=-90°
  //   center = (g11+g17, g20) = (g25, g20), endpoint = (g25, g20+g17) = (g25, g10)
  // Arc 4: from (hc, g10), wR=g17 hR=g17 stAng=90° swAng=-90°
  //   center = (hc, g10-g17), endpoint = (hc+g17, g10-g17)
  const fillIcon = [
    `M${g12},${g21}`,
    `L${g23},${g9}`,
    `L${hc},${g21}`,
    `L${g24},${g21}`,
    `L${g24},${g20}`,
    `A${g27},${g27} 0 0,1 ${g24 - g27},${g19}`, // arc 1: inner bottom-right corner
    `L${g25},${g19}`, // across inner bottom
    `A${g27},${g27} 0 0,1 ${g26},${g20}`, // arc 2: inner bottom-left corner
    `L${g26},${g21}`,
    `L${g11},${g21}`,
    `L${g11},${g20}`,
    `A${g17},${g17} 0 0,0 ${g25},${g10}`, // arc 3: outer bottom-left curve
    `L${hc},${g10}`, // across outer bottom
    `A${g17},${g17} 0 0,0 ${hc + g17},${g10 - g17}`, // arc 4: outer bottom-right curve
    `L${g22},${g21}`,
    `Z`,
  ].join(' ')

  // Outline path (path 2 in OOXML spec — traces shape with different arc winding)
  // Starts from right outer edge, traces clockwise: outer right → outer bottom → outer left → inner left → inner bottom → inner right → arrow
  // Arc A: from (g22, g20), wR=g17 hR=g17 stAng=0° swAng=90°
  //   center = (g22-g17, g20) = (g22-g17, g20), endpoint = (g22-g17, g20+g17)
  //   g22-g17 = g11+g14-g17 = g11 + g13*7/8 - g13*3/8 = g11 + g13/2 = g25 + g13/8 = hc? No.
  //   Actually: g22 = g11+g14, g14 = g13*7/8, g17 = g13*3/8
  //   g22 - g17 = g11 + g13*7/8 - g13*3/8 = g11 + g13*4/8 = g11 + g13/2 = hc (since hc = g11 + dx2 = g11 + g13/2)
  //   Hmm wait, dx2 = ss*3/8 and g13 = ss*3/4. So g13/2 = ss*3/8 = dx2. So hc = g11 + dx2 = g11 + g13/2. Yes!
  //   endpoint = (hc, g20+g17) = (hc, g10)? g20+g17 = (g9+g16)+g17 = g9+g13*5/8+g13*3/8 = g9+g13 = g9+ss*3/4
  //   g10 = vc+dx2. g9+g13 = (vc-dx2) + 2*dx2 = vc+dx2 = g10. Yes! endpoint = (hc, g10) ✓ but wait...
  //   Actually stAng=0° means start angle is 0°. center = (g22 - g17*cos(0), g20 - g17*sin(0)) = (g22-g17, g20).
  //   endAng = 0+90 = 90°. endX = center.x + g17*cos(90°) = g22-g17. endY = center.y + g17*sin(90°) = g20+g17.
  //   So endpoint = (g22-g17, g20+g17). Let's verify: g22-g17 = g11+g14-g17 = g11+g13*(7/8-3/8) = g11+g13/2 = g25+g13/8
  //   Hmm, g25 = g11+g17 = g11+g13*3/8. g11+g13/2 = g11+g13*4/8. That's not g25, it's g25 + g13/8.
  //   Actually let me just compute: g11+g13/2. g13/2 is not one of the named guides.
  //   OK, the spec says after this arc: lnTo (g25, g10). So endpoint.x must be something, then line to g25.
  //   endpoint.x = g22-g17 = g11+g14-g17 = g11+g13*7/8-g13*3/8 = g11+g13*4/8 = g11+g13/2.
  //   Then lnTo (g25, g10) where g25 = g11+g13*3/8.
  //   endpointY = g20+g17 = g10. So endpoint = (g11+g13/2, g10).
  //   Line from there to (g25, g10) is horizontal. Makes sense.
  // Arc B: from (g25, g10), wR=g17 hR=g17 stAng=90° swAng=90°
  //   center = (g25, g10-g17), endAng=180°
  //   endX = g25+g17*cos(180°) = g25-g17 = g11+g17-g17 = g11
  //   endY = (g10-g17)+g17*sin(180°) = g10-g17 = g20
  //   endpoint = (g11, g20). Then lnTo (g11, g21).
  // Arc C: from (g26, g20), wR=g27 hR=g27 stAng=180° swAng=-90°
  //   center = (g26+g27, g20) = (g26+g27, g20). g26+g27 = g11+g18+g13/8 = g11+g13/4+g13/8 = g11+g13*3/8 = g25
  //   endAng = 180-90 = 90°. endX = g25+g27*cos(90°) = g25. endY = g20+g27*sin(90°) = g20+g27 = g19.
  //   endpoint = (g25, g19). Hmm, but spec says lnTo(hc, g19) after this arc.
  //   Wait: lnTo before spec says `<lnTo><pt x="hc" y="g19"/></lnTo>`. So endpoint is (g25, g19), then line to (hc, g19).
  //   Hmm actually spec says: `<lnTo><pt x="hc" y="g19" /></lnTo>`.
  //   Wait no: `L(hc, g19)` in the spec.
  // Arc D: from (hc, g19), wR=g27 hR=g27 stAng=90° swAng=-90°
  //   center = (hc, g19-g27), endAng = 0°.
  //   endX = hc+g27*cos(0°) = hc+g27. g19-g27 = g20. endY = g20+g27*sin(0°) = g20.
  //   endpoint = (hc+g27, g20). Hmm, but g24 = g11+g16 = g11+g13*5/8.
  //   hc+g27 = g11+g13/2+g13/8 = g11+g13*5/8 = g24. So endpoint = (g24, g20).
  //   Then lnTo (g24, g21). Then lnTo (hc, g21). Then lnTo (g23, g9). Close.

  const outline = [
    `M${g12},${g21}`,
    `L${g22},${g21}`,
    `L${g22},${g20}`,
    `A${g17},${g17} 0 0,1 ${g11 + g13 / 2},${g10}`, // arc A: outer bottom-right (0°→90°)
    `L${g25},${g10}`, // across outer bottom
    `A${g17},${g17} 0 0,1 ${g11},${g20}`, // arc B: outer bottom-left (90°→180°)
    `L${g11},${g21}`,
    `L${g26},${g21}`,
    `L${g26},${g20}`,
    `A${g27},${g27} 0 0,0 ${g25},${g19}`, // arc C: inner bottom-left (180°→90°, CCW)
    `L${hc},${g19}`, // across inner bottom
    `A${g27},${g27} 0 0,0 ${g24},${g20}`, // arc D: inner bottom-right (90°→0°, CCW)
    `L${g24},${g21}`,
    `L${hc},${g21}`,
    `L${g23},${g9}`,
    `Z`,
  ].join(' ')

  return [
    { d: `${_rect(w, h)} ${fillIcon}`, fill: 'norm', stroke: false },
    { d: fillIcon, fill: 'darken', stroke: false },
    { d: outline, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonSound (VBA 0135): speaker icon with 3 sound wave lines
// OOXML spec: 4 paths – bg+speaker cutout (norm), speaker fill (darken), speaker outline+waves (stroke), rect outline (stroke)
multiPathPresets.set('actionButtonSound', (w, h) => {
  const { g9, g10, g11, g12, g13, hc: _hcS, vc } = _abGuides(w, h)
  // Guide calculations from OOXML presetShapeDefinitions.xml
  const g14 = g13 / 8
  const g15 = (g13 * 5) / 16
  const g16 = (g13 * 5) / 8
  const g17 = (g13 * 11) / 16
  const g18 = (g13 * 3) / 4
  const g19 = (g13 * 7) / 8

  // Absolute positions
  const g20 = g9 + g14
  const g21 = g9 + g15
  const g22 = g9 + g17
  const g23 = g9 + g19
  const g24 = g11 + g15
  const g25 = g11 + g16
  const g26 = g11 + g18

  // Speaker shape (pentagon-like)
  const speaker = `M${g11},${g21} L${g11},${g22} L${g24},${g22} L${g25},${g10} L${g25},${g9} L${g24},${g21} Z`

  // Outline path: speaker outline (different winding) + 3 sound wave lines
  const speakerOutline = `M${g11},${g21} L${g24},${g21} L${g25},${g9} L${g25},${g10} L${g24},${g22} L${g11},${g22} Z`

  const waveLine1 = `M${g26},${g21} L${g12},${g20}` // top-right diagonal
  const waveLine2 = `M${g26},${vc} L${g12},${vc}` // middle horizontal
  const waveLine3 = `M${g26},${g22} L${g12},${g23}` // bottom-right diagonal

  const outlineWithWaves = `${speakerOutline} ${waveLine1} ${waveLine2} ${waveLine3}`

  return [
    { d: `${_rect(w, h)} ${speaker}`, fill: 'norm', stroke: false },
    { d: speaker, fill: 'darken', stroke: false },
    { d: outlineWithWaves, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonInformation (VBA 0128): circle with "i" inside
multiPathPresets.set('actionButtonInformation', (w, h) => {
  const { g9, g10, g11, g13, hc, vc: _vcI, dx2 } = _abGuides(w, h)
  const g14 = g13 / 32
  const g17v = (g13 * 5) / 16
  const g18v = (g13 * 3) / 8
  const g19v = (g13 * 13) / 32
  const g20v = (g13 * 19) / 32
  const g22v = (g13 * 11) / 16
  const g23v = (g13 * 13) / 16
  const g24v = (g13 * 7) / 8
  const g38 = (g13 * 3) / 32
  const y25 = g9 + g14
  const y28 = g9 + g17v
  const y29 = g9 + g18v
  const y30 = g9 + g23v
  const y31 = g9 + g24v
  const x32 = g11 + g17v
  const x34 = g11 + g19v
  const x35 = g11 + g20v
  const x37 = g11 + g22v
  const circle = `M${hc},${g9} A${dx2},${dx2} 0 1,1 ${hc},${g10} A${dx2},${dx2} 0 1,1 ${hc},${g9} Z`
  const dot = `M${hc},${y25} A${g38},${g38} 0 1,1 ${hc},${y25 + g38 * 2} A${g38},${g38} 0 1,1 ${hc},${y25} Z`
  const iBody = `M${x32},${y28} L${x37},${y28} L${x37},${y29} L${x35},${y29} L${x35},${y30} L${x37},${y30} L${x37},${y31} L${x32},${y31} L${x32},${y30} L${x34},${y30} L${x34},${y29} L${x32},${y29} Z`
  const iconInner = `${dot} ${iBody}`
  return [
    { d: `${_rect(w, h)} ${circle}`, fill: 'norm', stroke: false },
    { d: `${circle} ${iconInner}`, fill: 'darken', stroke: false },
    { d: iconInner, fill: 'lighten', stroke: false },
    { d: `${circle} ${iconInner}`, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonHome (VBA 0126): house icon with chimney and door
// OOXML spec: 5 paths – bg+house cutout (norm), walls+chimney (darkenLess), roof+door (darken),
// icon outline (stroke), rect outline (stroke)
multiPathPresets.set('actionButtonHome', (w, h) => {
  const { g9, g10, g11, g12, g13, hc, vc } = _abGuides(w, h)
  // Guide calculations from OOXML presetShapeDefinitions.xml
  const g14 = g13 / 16
  const g15 = g13 / 8
  const g16 = (g13 * 3) / 16
  const g17 = (g13 * 5) / 16
  const g18 = (g13 * 7) / 16
  const g19 = (g13 * 9) / 16
  const g20 = (g13 * 11) / 16
  const g21 = (g13 * 3) / 4
  const g22 = (g13 * 13) / 16
  const g23 = (g13 * 7) / 8

  // Absolute positions
  const g24 = g9 + g14
  const g25 = g9 + g16
  const g26 = g9 + g17
  const g27 = g9 + g21
  const g28 = g11 + g15
  const g29 = g11 + g18
  const g30 = g11 + g19
  const g31 = g11 + g20
  const g32 = g11 + g22
  const g33 = g11 + g23

  // Path 0: background rect + full house outline cutout (norm, no stroke)
  // House outline: roof triangle → right side → chimney → left side → base
  const houseOutline =
    `M${hc},${g9} ` +
    `L${g11},${vc} L${g28},${vc} L${g28},${g10} L${g33},${g10} L${g33},${vc} L${g12},${vc} ` +
    `L${g32},${g26} L${g32},${g24} L${g31},${g24} L${g31},${g25} Z`

  // Path 1: walls + chimney (darkenLess, no stroke)
  // Sub-path 1: chimney bar
  const chimney = `M${g32},${g26} L${g32},${g24} L${g31},${g24} L${g31},${g25} Z`
  // Sub-path 2: house body (walls) with door cutout
  const walls = `M${g28},${vc} L${g28},${g10} L${g29},${g10} L${g29},${g27} L${g30},${g27} L${g30},${g10} L${g33},${g10} L${g33},${vc} Z`

  // Path 2: roof triangle + door (darken, no stroke)
  const roof = `M${hc},${g9} L${g11},${vc} L${g12},${vc} Z`
  const door = `M${g29},${g27} L${g30},${g27} L${g30},${g10} L${g29},${g10} Z`

  // Path 3: icon outline with all detail lines (none fill, stroke)
  const iconOutline =
    `M${hc},${g9} ` +
    `L${g31},${g25} L${g31},${g24} L${g32},${g24} L${g32},${g26} L${g12},${vc} ` +
    `L${g33},${vc} L${g33},${g10} L${g28},${g10} L${g28},${vc} L${g11},${vc} Z ` +
    // Chimney diagonal line
    `M${g31},${g25} L${g32},${g26} ` +
    // Horizontal eave line
    `M${g33},${vc} L${g28},${vc} ` +
    // Door outline
    `M${g29},${g10} L${g29},${g27} L${g30},${g27} L${g30},${g10}`

  return [
    { d: `${_rect(w, h)} ${houseOutline}`, fill: 'norm', stroke: false },
    { d: `${chimney} ${walls}`, fill: 'darkenLess', stroke: false },
    { d: `${roof} ${door}`, fill: 'darken', stroke: false },
    { d: iconOutline, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonHelp (VBA 0127): question mark "?" inside rectangle
// OOXML spec: 4 paths – bg+icon cutout (norm), icon fill (darken), icon outline (stroke), rect outline (stroke)
multiPathPresets.set('actionButtonHelp', (w, h) => {
  const { g9, g11, g13, hc, vc: _vcH } = _abGuides(w, h)
  // Guide calculations from OOXML presetShapeDefinitions.xml
  const g14 = g13 / 7
  const g15 = (g13 * 3) / 14
  const g16 = (g13 * 2) / 7
  const g19 = (g13 * 3) / 7
  const g20 = (g13 * 4) / 7
  const g21 = (g13 * 17) / 28
  const g23 = (g13 * 21) / 28
  const g24 = (g13 * 11) / 14
  const g41 = g13 / 14
  const g42 = (g13 * 3) / 28

  // Absolute positions
  const g27 = g9 + g16
  const g29 = g9 + g21
  const g30 = g9 + g23
  const g31 = g9 + g24
  const g33 = g11 + g15
  const g36 = g11 + g19
  const g37 = g11 + g20

  // Helper: OOXML arcTo → SVG arc segment
  // Computes endpoint from center (derived from current point + start angle) and returns SVG A command
  const arcSeg = (
    curX: number,
    curY: number,
    wR: number,
    hR: number,
    stDeg: number,
    swDeg: number
  ) => {
    const stRad = (stDeg * Math.PI) / 180
    const endRad = ((stDeg + swDeg) * Math.PI) / 180
    const cx = curX - wR * Math.cos(stRad)
    const cy = curY - hR * Math.sin(stRad)
    const endX = cx + wR * Math.cos(endRad)
    const endY = cy + hR * Math.sin(endRad)
    const largeArc = Math.abs(swDeg) > 180 ? 1 : 0
    const sweep = swDeg > 0 ? 1 : 0
    return { endX, endY, svg: `A${wR},${hR} 0 ${largeArc},${sweep} ${endX},${endY}` }
  }

  // Build question mark path following OOXML arcTo sequence exactly
  // Start at (g33, g27)
  let cx = g33
  let cy = g27

  // Arc 1: wR=g16 hR=g16 stAng=180° swAng=180° (top semicircle, clockwise)
  const a1 = arcSeg(cx, cy, g16, g16, 180, 180)
  cx = a1.endX
  cy = a1.endY

  // Arc 2: wR=g14 hR=g15 stAng=0° swAng=90° (curve down right)
  const a2 = arcSeg(cx, cy, g14, g15, 0, 90)
  cx = a2.endX
  cy = a2.endY

  // Arc 3: wR=g41 hR=g42 stAng=270° swAng=-90° (small reverse curve)
  const a3 = arcSeg(cx, cy, g41, g42, 270, -90)
  // After arc 3, lines to stem
  // lnTo (g37, g30), (g36, g30), (g36, g29)
  // then more arcs back up

  // Arc 4: wR=g14 hR=g15 stAng=180° swAng=90° (inner curve going up)
  const a4 = arcSeg(g36, g29, g14, g15, 180, 90)

  // Arc 5: wR=g41 hR=g42 stAng=90° swAng=-90° (small inner reverse curve)
  const a5 = arcSeg(a4.endX, a4.endY, g41, g42, 90, -90)

  // Arc 6: wR=g14 hR=g14 stAng=0° swAng=-180° (inner top semicircle, counter-clockwise)
  const a6 = arcSeg(a5.endX, a5.endY, g14, g14, 0, -180)

  // Bottom dot circle at (hc, g31) with radius g42
  const dot = `M${hc},${g31} A${g42},${g42} 0 1,1 ${hc},${g31 + g42 * 2} A${g42},${g42} 0 1,1 ${hc},${g31} Z`

  // Question mark path (outer shape with arcs + stem + inner cutout arcs)
  const qMark =
    `M${g33},${g27} ` +
    `${a1.svg} ` +
    `${a2.svg} ` +
    `${a3.svg} ` +
    `L${g37},${g30} L${g36},${g30} L${g36},${g29} ` +
    `${a4.svg} ` +
    `${a5.svg} ` +
    `${a6.svg} Z`

  const icon = `${qMark} ${dot}`

  return [
    { d: `${_rect(w, h)} ${icon}`, fill: 'norm', stroke: false }, // Background with icon cutout
    { d: icon, fill: 'darken', stroke: false }, // Darkened icon fill
    { d: icon, fill: 'none', stroke: true }, // Icon outline
    { d: _rect(w, h), fill: 'none', stroke: true }, // Rect outline
  ]
})

// actionButtonDocument (VBA 0134): document with folded corner
multiPathPresets.set('actionButtonDocument', (w, h) => {
  const ss = Math.min(w, h)
  const hc = w / 2
  const vc = h / 2
  const dx2 = (ss * 3) / 8
  const dx1 = (ss * 9) / 32
  const g9 = vc - dx2
  const g10 = vc + dx2
  const g11 = hc - dx1
  const g12 = hc + dx1
  const g13 = (ss * 3) / 16
  const g14 = g12 - g13
  const g15 = g9 + g13
  const doc = `M${g11},${g9} L${g14},${g9} L${g12},${g15} L${g12},${g10} L${g11},${g10} Z`
  const fold = `M${g14},${g9} L${g14},${g15} L${g12},${g15} Z`
  const outline = `${doc} M${g12},${g15} L${g14},${g15} L${g14},${g9}`
  return [
    { d: `${_rect(w, h)} ${doc}`, fill: 'norm', stroke: false },
    { d: doc, fill: 'darkenLess', stroke: false },
    { d: fold, fill: 'darken', stroke: false },
    { d: outline, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// actionButtonMovie (VBA 0136): film strip / camera icon
multiPathPresets.set('actionButtonMovie', (w, h) => {
  const { g9, g11, g12, g13 } = _abGuides(w, h)
  // Guide values from OOXML presetShapeDefinitions.xml (fractions of g13 = ss*3/4)
  const g14 = (g13 * 1455) / 21600
  const g15 = (g13 * 1905) / 21600
  const g16 = (g13 * 2325) / 21600
  const g17 = (g13 * 16155) / 21600
  const g18 = (g13 * 17010) / 21600
  const g19 = (g13 * 19335) / 21600
  const g20 = (g13 * 19725) / 21600
  const g21 = (g13 * 20595) / 21600
  const g22 = (g13 * 5280) / 21600
  const g23 = (g13 * 5730) / 21600
  const g24 = (g13 * 6630) / 21600
  const g25 = (g13 * 7492) / 21600
  const g26 = (g13 * 9067) / 21600
  const g27 = (g13 * 9555) / 21600
  const g28 = (g13 * 13342) / 21600
  const g29 = (g13 * 14580) / 21600
  const g30 = (g13 * 15592) / 21600
  // Composite guides: x = g11 + gN, y = g9 + gN
  const x31 = g11 + g14
  const x32 = g11 + g15
  const x33 = g11 + g16
  const x34 = g11 + g17
  const x35 = g11 + g18
  const x36 = g11 + g19
  const x37 = g11 + g20
  const x38 = g11 + g21
  const y39 = g9 + g22
  const y40 = g9 + g23
  const y41 = g9 + g24
  const y42 = g9 + g25
  const y43 = g9 + g26
  const y44 = g9 + g27
  const y45 = g9 + g28
  const y46 = g9 + g29
  const y47 = g9 + g30
  const icon = [
    `M${g11},${y39}`,
    `L${g11},${y44}`,
    `L${x31},${y44}`,
    `L${x32},${y43}`,
    `L${x33},${y43}`,
    `L${x33},${y47}`,
    `L${x35},${y47}`,
    `L${x35},${y45}`,
    `L${x36},${y45}`,
    `L${x38},${y46}`,
    `L${g12},${y46}`,
    `L${g12},${y41}`,
    `L${x38},${y41}`,
    `L${x37},${y42}`,
    `L${x35},${y42}`,
    `L${x35},${y41}`,
    `L${x34},${y40}`,
    `L${x32},${y40}`,
    `L${x31},${y39}`,
    `Z`,
  ].join(' ')
  return [
    { d: `${_rect(w, h)} ${icon}`, fill: 'norm', stroke: false },
    { d: icon, fill: 'darken', stroke: false },
    { d: icon, fill: 'none', stroke: true },
    { d: _rect(w, h), fill: 'none', stroke: true },
  ]
})

// flowChartOfflineStorage (VBA 0139): inverted triangle with horizontal base line
multiPathPresets.set('flowChartOfflineStorage', (w, h) => {
  const tri = `M0,0 L${w},0 L${w / 2},${h} Z`
  const lineY = (h * 4) / 5
  const line = `M${(w * 2) / 5},${lineY} L${(w * 3) / 5},${lineY}`
  return [
    { d: tri, fill: 'norm', stroke: false },
    { d: line, fill: 'none', stroke: true },
    { d: tri, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('cube', (w, h, adjustments) => {
  const a = Math.min(Math.max(adj(adjustments, 'adj', 25000), 0), 0.45)
  const depth = Math.min(w, h) * a
  const front = [
    `M0,${depth}`,
    `L${w - depth},${depth}`,
    `L${w - depth},${h}`,
    `L0,${h}`,
    'Z',
  ].join(' ')
  const top = [`M0,${depth}`, `L${depth},0`, `L${w},0`, `L${w - depth},${depth}`, 'Z'].join(' ')
  const right = [
    `M${w - depth},${depth}`,
    `L${w},0`,
    `L${w},${h - depth}`,
    `L${w - depth},${h}`,
    'Z',
  ].join(' ')
  return [
    { d: front, fill: 'norm', stroke: true },
    { d: top, fill: 'lightenLess', stroke: true },
    { d: right, fill: 'darkenLess', stroke: true },
  ]
})

multiPathPresets.set('bevel', (w, h, adjustments) => {
  // OOXML bevel: picture-frame shape with 4 beveled faces + center rect.
  // adj = bevel thickness (default 12500 = 12.5% of min(w,h))
  const a = Math.min(Math.max(adj(adjustments, 'adj', 12500), 0), 0.45)
  const t = Math.min(w, h) * a
  const inner = `M${t},${t} L${w - t},${t} L${w - t},${h - t} L${t},${h - t} Z`
  const top = `M0,0 L${w},0 L${w - t},${t} L${t},${t} Z`
  const bottom = `M0,${h} L${t},${h - t} L${w - t},${h - t} L${w},${h} Z`
  const left = `M0,0 L${t},${t} L${t},${h - t} L0,${h} Z`
  const right = `M${w},0 L${w},${h} L${w - t},${h - t} L${w - t},${t} Z`
  return [
    { d: inner, fill: 'norm', stroke: true },
    { d: top, fill: 'lightenLess', stroke: true },
    { d: right, fill: 'darkenLess', stroke: true },
    { d: bottom, fill: 'darken', stroke: true },
    { d: left, fill: 'lighten', stroke: true },
  ]
})

multiPathPresets.set('leftRightRibbon', (w, h, adjustments) => {
  // OOXML leftRightRibbon: 3-path shape (body + center fold shadow + stroke outline).
  // adj1=50000 (band height), adj2=50000 (notch width), adj3=16667 (wave amplitude).
  const ss = Math.min(w, h)
  const wd2 = w / 2
  const wd32 = w / 32
  const hc = w / 2
  const vc = h / 2

  const a3 = Math.min(Math.max((adjustments?.get('adj3') ?? 16667) / 100000, 0), 0.33333)
  const maxAdj1 = 1 - a3
  const a1 = Math.min(Math.max((adjustments?.get('adj1') ?? 50000) / 100000, 0), maxAdj1)
  const w1 = wd2 - wd32
  const maxAdj2 = w1 / ss
  const a2 = Math.min(Math.max((adjustments?.get('adj2') ?? 50000) / 100000, 0), maxAdj2)

  const x1 = ss * a2
  const x4 = w - x1
  const dy1 = (h * a1) / 2
  const dy2 = (-h * a3) / 2

  const ly1 = vc + dy2 - dy1
  const ry4 = vc + dy1 - dy2
  const ly2 = ly1 + dy1
  const ry3 = h - ly2
  const ly4 = ly2 * 2
  const ry1 = h - ly4
  const ly3 = ly4 - ly1
  const ry2 = h - ly3

  const hR = (a3 * ss) / 4
  const x2 = hc - wd32
  const x3 = hc + wd32
  const y1 = ly1 + hR
  const y2 = ry2 - hR

  // Helper: compute OOXML arcTo → SVG arc segment
  const arcTo = (
    curX: number,
    curY: number,
    wR: number,
    hRad: number,
    stDeg: number,
    swDeg: number
  ) => {
    const stRad = (stDeg * Math.PI) / 180
    const endRad = ((stDeg + swDeg) * Math.PI) / 180
    const cx = curX - wR * Math.cos(stRad)
    const cy = curY - hRad * Math.sin(stRad)
    const endX = cx + wR * Math.cos(endRad)
    const endY = cy + hRad * Math.sin(endRad)
    const largeArc = Math.abs(swDeg) > 180 ? 1 : 0
    const sweep = swDeg > 0 ? 1 : 0
    return { endX, endY, svg: `A${wR},${hRad} 0 ${largeArc},${sweep} ${endX},${endY}` }
  }

  // Path 1: Main body (fill, no stroke)
  const cx1 = hc
  const cy1 = ly1 // after lnTo (hc, ly1)
  const arc1a = arcTo(cx1, cy1, wd32, hR, 270, 180)
  const arc1b = arcTo(arc1a.endX, arc1a.endY, wd32, hR, 270, -180)
  const cx1c = hc
  const cy1c = ry4 // after lnTo (hc, ry4)
  const arc1c = arcTo(cx1c, cy1c, wd32, hR, 90, 90)

  const body = [
    `M0,${ly2}`,
    `L${x1},0`,
    `L${x1},${ly1}`,
    `L${hc},${ly1}`,
    arc1a.svg,
    arc1b.svg,
    `L${x4},${ry2}`,
    `L${x4},${ry1}`,
    `L${w},${ry3}`,
    `L${x4},${h}`,
    `L${x4},${ry4}`,
    `L${hc},${ry4}`,
    arc1c.svg,
    `L${x2},${ly3}`,
    `L${x1},${ly3}`,
    `L${x1},${ly4}`,
    'Z',
  ].join(' ')

  // Path 2: Center fold shadow (darkenLess, no stroke)
  const arc2a = arcTo(x3, y1, wd32, hR, 0, 90)
  const arc2b = arcTo(arc2a.endX, arc2a.endY, wd32, hR, 270, -180)

  const shadow = [`M${x3},${y1}`, arc2a.svg, arc2b.svg, `L${x3},${ry2}`, 'Z'].join(' ')

  // Path 3: Stroke outline (no fill) — same as body + interior fold lines
  const outline = [body, `M${x3},${y1} L${x3},${ry2}`, `M${x2},${y2} L${x2},${ly3}`].join(' ')

  return [
    { d: body, fill: 'norm', stroke: false },
    { d: shadow, fill: 'darkenLess', stroke: false },
    { d: outline, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('ellipseRibbon', (w, h, adjustments) => {
  // OOXML ellipseRibbon: ribbon with parabolic curved bottom edge
  // 3 paths: body (fill=norm), darkenLess shadow folds, outline (fill=none)
  const adj1 = adjustments?.get('adj1') ?? 25000
  const adj2 = adjustments?.get('adj2') ?? 50000
  const adj3 = adjustments?.get('adj3') ?? 12500

  const a1 = Math.max(0, Math.min(adj1, 100000))
  const a2 = Math.max(25000, Math.min(adj2, 75000))
  const q10 = 100000 - a1
  const q11 = q10 / 2
  const q12 = a1 - q11
  const minAdj3 = Math.max(0, q12)
  const a3 = Math.max(minAdj3, Math.min(adj3, a1))

  const dx2 = (w * a2) / 200000
  const x2 = w / 2 - dx2
  const x3 = x2 + w / 8
  const x4 = w - x3
  const x5 = w - x2
  const x6 = w - w / 8

  const dy1 = (h * a3) / 100000
  const f1 = w > 0 ? (4 * dy1) / w : 0
  // Parabola: p(x) = f1 * x * (1 - x/w)
  const parab = (x: number) => f1 * (x - (x * x) / w)

  const y1 = parab(x3)
  const cx1 = x3 / 2
  const cy1 = f1 * cx1 // Bezier control (approximation)
  const cx2 = w - cx1

  // q1 redefined: total fold height
  const q1 = (h * a1) / 100000
  const dy3 = q1 - dy1

  const q5 = parab(x2)
  const y3 = q5 + dy3

  const q6 = dy1 + dy3 - y3
  const q7 = q6 + dy1
  const cy3 = q7 + dy3

  const rh = h - q1

  const q8 = (dy1 * 14) / 16
  const y2 = (q8 + rh) / 2

  const y5 = q5 + rh
  const y6 = y3 + rh

  const cx4 = x2 / 2
  const cy4 = f1 * cx4 + rh
  const cx5 = w - cx4

  const cy6 = cy3 + rh

  const y7 = y1 + dy3
  const cy7 = q1 + q1 - y7

  const hc = w / 2
  const wd8 = w / 8

  // Path 1: body fill (stroke=false)
  const body = [
    `M0,0`,
    `Q${cx1},${cy1} ${x3},${y1}`,
    `L${x2},${y3}`,
    `Q${hc},${cy3} ${x5},${y3}`,
    `L${x4},${y1}`,
    `Q${cx2},${cy1} ${w},0`,
    `L${x6},${y2}`,
    `L${w},${rh}`,
    `Q${cx5},${cy4} ${x5},${y5}`,
    `L${x5},${y6}`,
    `Q${hc},${cy6} ${x2},${y6}`,
    `L${x2},${y5}`,
    `Q${cx4},${cy4} 0,${rh}`,
    `L${wd8},${y2}`,
    `Z`,
  ].join(' ')

  // Path 2: darkenLess shadow folds (stroke=false)
  const shadow = [
    `M${x3},${y7}`,
    `L${x3},${y1}`,
    `L${x2},${y3}`,
    `Q${hc},${cy3} ${x5},${y3}`,
    `L${x4},${y1}`,
    `L${x4},${y7}`,
    `Q${hc},${cy7} ${x3},${y7}`,
    `Z`,
  ].join(' ')

  // Path 3: outline (fill=none)
  const outline = [
    `M0,0`,
    `Q${cx1},${cy1} ${x3},${y1}`,
    `L${x2},${y3}`,
    `Q${hc},${cy3} ${x5},${y3}`,
    `L${x4},${y1}`,
    `Q${cx2},${cy1} ${w},0`,
    `L${x6},${y2}`,
    `L${w},${rh}`,
    `Q${cx5},${cy4} ${x5},${y5}`,
    `L${x5},${y6}`,
    `Q${hc},${cy6} ${x2},${y6}`,
    `L${x2},${y5}`,
    `Q${cx4},${cy4} 0,${rh}`,
    `L${wd8},${y2}`,
    `Z`,
    `M${x2},${y5} L${x2},${y3}`,
    `M${x5},${y3} L${x5},${y5}`,
    `M${x3},${y1} L${x3},${y7}`,
    `M${x4},${y7} L${x4},${y1}`,
  ].join(' ')

  return [
    { d: body, fill: 'norm', stroke: false },
    { d: shadow, fill: 'darkenLess', stroke: false },
    { d: outline, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('ellipseRibbon2', (w, h, adjustments) => {
  // OOXML ellipseRibbon2: inverted ribbon with parabolic curved top edge
  // 3 paths: body (fill=norm), darkenLess shadow folds, outline (fill=none)
  // All y-values computed as b - value (measured from bottom)
  const adj1 = adjustments?.get('adj1') ?? 25000
  const adj2 = adjustments?.get('adj2') ?? 50000
  const adj3 = adjustments?.get('adj3') ?? 12500

  const a1 = Math.max(0, Math.min(adj1, 100000))
  const a2 = Math.max(25000, Math.min(adj2, 75000))
  const q10 = 100000 - a1
  const q11 = q10 / 2
  const q12 = a1 - q11
  const minAdj3 = Math.max(0, q12)
  const a3 = Math.max(minAdj3, Math.min(adj3, a1))

  const b = h
  const dx2 = (w * a2) / 200000
  const x2 = w / 2 - dx2
  const x3 = x2 + w / 8
  const x4 = w - x3
  const x5 = w - x2
  const x6 = w - w / 8

  const dy1 = (h * a3) / 100000
  const f1 = w > 0 ? (4 * dy1) / w : 0

  // u1 = parabola at x3
  const u1 = f1 * (x3 - (x3 * x3) / w)
  const y1 = b - u1

  const cx1 = x3 / 2
  const cu1 = f1 * cx1
  const cy1 = b - cu1
  const cx2 = w - cx1

  // q1 redefined: total fold height
  const q1 = (h * a1) / 100000
  const dy3 = q1 - dy1

  const q5 = f1 * (x2 - (x2 * x2) / w)
  const u3 = q5 + dy3
  const y3 = b - u3

  const q6 = dy1 + dy3 - u3
  const q7 = q6 + dy1
  const cu3 = q7 + dy3
  const cy3 = b - cu3

  const rh = b - q1

  const q8 = (dy1 * 14) / 16
  const u2 = (q8 + rh) / 2
  const y2 = b - u2

  const u5 = q5 + rh
  const y5 = b - u5

  const u6 = u3 + rh
  const y6 = b - u6

  const cx4 = x2 / 2
  const cu4 = f1 * cx4 + rh
  const cy4 = b - cu4
  const cx5 = w - cx4

  const cu6 = cu3 + rh
  const cy6 = b - cu6

  const u7 = u1 + dy3
  const y7 = b - u7
  const cu7 = q1 + q1 - u7
  const cy7 = b - cu7

  const hc = w / 2
  const wd8 = w / 8

  // Path 1: body fill (stroke=false)
  const body = [
    `M0,${b}`,
    `Q${cx1},${cy1} ${x3},${y1}`,
    `L${x2},${y3}`,
    `Q${hc},${cy3} ${x5},${y3}`,
    `L${x4},${y1}`,
    `Q${cx2},${cy1} ${w},${b}`,
    `L${x6},${y2}`,
    `L${w},${q1}`,
    `Q${cx5},${cy4} ${x5},${y5}`,
    `L${x5},${y6}`,
    `Q${hc},${cy6} ${x2},${y6}`,
    `L${x2},${y5}`,
    `Q${cx4},${cy4} 0,${q1}`,
    `L${wd8},${y2}`,
    `Z`,
  ].join(' ')

  // Path 2: darkenLess shadow folds (stroke=false)
  const shadow = [
    `M${x3},${y7}`,
    `L${x3},${y1}`,
    `L${x2},${y3}`,
    `Q${hc},${cy3} ${x5},${y3}`,
    `L${x4},${y1}`,
    `L${x4},${y7}`,
    `Q${hc},${cy7} ${x3},${y7}`,
    `Z`,
  ].join(' ')

  // Path 3: outline (fill=none)
  const outline = [
    `M0,${b}`,
    `L${wd8},${y2}`,
    `L0,${q1}`,
    `Q${cx4},${cy4} ${x2},${y5}`,
    `L${x2},${y6}`,
    `Q${hc},${cy6} ${x5},${y6}`,
    `L${x5},${y5}`,
    `Q${cx5},${cy4} ${w},${q1}`,
    `L${x6},${y2}`,
    `L${w},${b}`,
    `Q${cx2},${cy1} ${x4},${y1}`,
    `L${x5},${y3}`,
    `Q${hc},${cy3} ${x2},${y3}`,
    `L${x3},${y1}`,
    `Q${cx1},${cy1} 0,${b}`,
    `Z`,
    `M${x2},${y3} L${x2},${y5}`,
    `M${x5},${y5} L${x5},${y3}`,
    `M${x3},${y7} L${x3},${y1}`,
    `M${x4},${y1} L${x4},${y7}`,
  ].join(' ')

  return [
    { d: body, fill: 'norm', stroke: false },
    { d: shadow, fill: 'darkenLess', stroke: false },
    { d: outline, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('smileyFace', (w, h, adjustments) => {
  // OOXML smileyFace: 4 paths — face(norm), eyes(darkenLess), smile(none), outline(none+stroke)
  const wd2 = w / 2
  const hd2 = h / 2
  const hc = w / 2
  const vc = h / 2

  // Adjustment: smile amplitude (default 4653, range -4653..4653)
  const rawAdj = adjustments?.get('adj') ?? 4653
  const a = Math.max(-4653, Math.min(rawAdj, 4653))

  // Eye positions (OOXML exact)
  const x2 = (w * 6215) / 21600
  const x3 = (w * 13135) / 21600
  const y1 = (h * 7570) / 21600
  const wR = (w * 1125) / 21600
  const hR = (h * 1125) / 21600

  // Smile curve positions (OOXML exact)
  const x1 = (w * 4969) / 21699
  const x4 = (w * 16640) / 21600
  const y3 = (h * 16515) / 21600
  const dy2 = (h * a) / 100000
  const y2 = y3 - dy2
  const y4 = y3 + dy2
  const dy3 = (h * a) / 50000
  const y5 = y4 + dy3

  // Path 1: face ellipse (fill=norm, stroke=false) — two half-arcs for full circle
  const face = `M${w},${vc} A${wd2},${hd2} 0 1,1 0,${vc} A${wd2},${hd2} 0 1,1 ${w},${vc} Z`

  // Path 2: eyes (fill=darkenLess) — two small ellipses at OOXML positions (two half-arcs each)
  const leftEye = `M${(x2 + wR).toFixed(2)},${y1.toFixed(2)} A${wR.toFixed(2)},${hR.toFixed(2)} 0 1,1 ${(x2 - wR).toFixed(2)},${y1.toFixed(2)} A${wR.toFixed(2)},${hR.toFixed(2)} 0 1,1 ${(x2 + wR).toFixed(2)},${y1.toFixed(2)} Z`
  const rightEye = `M${(x3 + wR).toFixed(2)},${y1.toFixed(2)} A${wR.toFixed(2)},${hR.toFixed(2)} 0 1,1 ${(x3 - wR).toFixed(2)},${y1.toFixed(2)} A${wR.toFixed(2)},${hR.toFixed(2)} 0 1,1 ${(x3 + wR).toFixed(2)},${y1.toFixed(2)} Z`

  // Path 3: smile (fill=none) — quadratic Bezier (OOXML quadBezTo)
  const smile = `M${x1.toFixed(2)},${y2.toFixed(2)} Q${hc.toFixed(2)},${y5.toFixed(2)} ${x4.toFixed(2)},${y2.toFixed(2)}`

  // Path 4: face outline (fill=none, stroke=true) — same as path 1
  const outline = `M${w},${vc} A${wd2},${hd2} 0 1,1 0,${vc} A${wd2},${hd2} 0 1,1 ${w},${vc} Z`

  return [
    { d: face, fill: 'norm', stroke: false },
    { d: `${leftEye} ${rightEye}`, fill: 'darkenLess', stroke: false },
    { d: smile, fill: 'none', stroke: true },
    { d: outline, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('foldedCorner', (w, h, adjustments) => {
  const a = adj(adjustments, 'adj', 16667)
  const fold = Math.min(w, h) * a * 0.7
  const body = `M0,0 L${w},0 L${w},${h - fold} L${w - fold},${h} L0,${h} Z`
  const foldFace = `M${w - fold},${h} L${w - fold},${h - fold} L${w},${h - fold} Z`
  const crease = `M${w - fold},${h} L${w - fold},${h - fold}`
  return [
    { d: body, fill: 'norm', stroke: true },
    { d: foldFace, fill: 'darkenLess', stroke: false },
    { d: crease, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('can', (w, h, adjustments) => {
  // OOXML: 3 paths — body (norm), top face (lighten), outline (stroke-only)
  const ss = Math.min(w, h)
  const maxAdj = (50000 * h) / ss
  const a = Math.min(Math.max(adjustments?.get('adj') ?? 25000, 0), maxAdj)
  const y1 = (ss * a) / 200000
  const y3 = h - y1
  const wd2 = w / 2
  const arcSeg = (
    curX: number,
    curY: number,
    wR: number,
    hR: number,
    stDeg: number,
    swDeg: number
  ) => {
    const stRad = (stDeg * Math.PI) / 180
    const endRad = ((stDeg + swDeg) * Math.PI) / 180
    const cx = curX - wR * Math.cos(stRad)
    const cy = curY - hR * Math.sin(stRad)
    const endX = cx + wR * Math.cos(endRad)
    const endY = cy + hR * Math.sin(endRad)
    const largeArc = Math.abs(swDeg) > 180 ? 1 : 0
    const sweep = swDeg > 0 ? 1 : 0
    return { endX, endY, svg: `A${wR},${hR} 0 ${largeArc},${sweep} ${endX},${endY}` }
  }
  // Path 1: Body (stroke:false, fill:norm)
  const a1 = arcSeg(0, y1, wd2, y1, 180, -180)
  const a2 = arcSeg(w, y3, wd2, y1, 0, 180)
  const body = `M0,${y1} ${a1.svg} L${w},${y3} ${a2.svg} Z`
  // Path 2: Top face (stroke:false, fill:lighten)
  const a3 = arcSeg(0, y1, wd2, y1, 180, 180)
  const a4 = arcSeg(a3.endX, a3.endY, wd2, y1, 0, 180)
  const topFace = `M0,${y1} ${a3.svg} ${a4.svg} Z`
  // Path 3: Outline (fill:none, stroke:true)
  const a5 = arcSeg(w, y1, wd2, y1, 0, 180)
  const a6 = arcSeg(a5.endX, a5.endY, wd2, y1, 180, 180)
  const a7 = arcSeg(w, y3, wd2, y1, 0, 180)
  const outline = `M${w},${y1} ${a5.svg} ${a6.svg} L${w},${y3} ${a7.svg} L0,${y1}`
  return [
    { d: body, fill: 'norm', stroke: false },
    { d: topFace, fill: 'lighten', stroke: false },
    { d: outline, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('curvedrightarrow', (w, h, adjustments) =>
  buildCurvedArrowMultiPath('curvedRightArrow', w, h, adjustments)
)

multiPathPresets.set('curvedleftarrow', (w, h, adjustments) =>
  buildCurvedArrowMultiPath('curvedLeftArrow', w, h, adjustments)
)

multiPathPresets.set('curveduparrow', (w, h, adjustments) =>
  buildCurvedVerticalArrowMultiPath('curvedUpArrow', w, h, adjustments)
)

multiPathPresets.set('curveddownarrow', (w, h, adjustments) =>
  buildCurvedVerticalArrowMultiPath('curvedDownArrow', w, h, adjustments)
)

multiPathPresets.set('bordercallout1', (w, h, adjustments) => {
  // OOXML: filled+stroked rectangle body + separate leader line (stroke-only).
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 112500)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -38333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('accentcallout1', (w, h, adjustments) => {
  // OOXML: filled rect + accent bar at x1 + 1-segment callout line
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 112500)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -38333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${x1},0 L${x1},${h}`, fill: 'none', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('accentcallout2', (w, h, adjustments) => {
  // OOXML: filled rect + accent bar at x1 + 2-segment callout line
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 112500)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -46667)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${x1},0 L${x1},${h}`, fill: 'none', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('accentcallout3', (w, h, adjustments) => {
  // OOXML: filled rect + accent bar at x1 + 3-segment callout line
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 100000)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -16667)) / 100000
  const y4 = (h * (adjustments?.get('adj7') ?? 112963)) / 100000
  const x4 = (w * (adjustments?.get('adj8') ?? -8333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${x1},0 L${x1},${h}`, fill: 'none', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3} L${x4},${y4}`, fill: 'none', stroke: true },
  ]
})

// --- callout1/2/3: filled rect (no stroke) + callout line segments ---
multiPathPresets.set('callout1', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 112500)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -38333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${x1},${y1} L${x2},${y2}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('callout2', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 112500)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -46667)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('callout3', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 100000)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -16667)) / 100000
  const y4 = (h * (adjustments?.get('adj7') ?? 112963)) / 100000
  const x4 = (w * (adjustments?.get('adj8') ?? -8333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3} L${x4},${y4}`, fill: 'none', stroke: true },
  ]
})

// --- borderCallout2/3: filled+stroked rect + callout line segments ---
multiPathPresets.set('bordercallout2', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 112500)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -46667)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('bordercallout3', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 100000)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -16667)) / 100000
  const y4 = (h * (adjustments?.get('adj7') ?? 112963)) / 100000
  const x4 = (w * (adjustments?.get('adj8') ?? -8333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3} L${x4},${y4}`, fill: 'none', stroke: true },
  ]
})

// --- accentBorderCallout1/2/3: filled+stroked rect + accent bar + callout line ---
multiPathPresets.set('accentbordercallout1', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 112500)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -38333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: true },
    { d: `M${x1},0 L${x1},${h}`, fill: 'none', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('accentbordercallout2', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 112500)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -46667)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: true },
    { d: `M${x1},0 L${x1},${h}`, fill: 'none', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('accentbordercallout3', (w, h, adjustments) => {
  const y1 = (h * (adjustments?.get('adj1') ?? 18750)) / 100000
  const x1 = (w * (adjustments?.get('adj2') ?? -8333)) / 100000
  const y2 = (h * (adjustments?.get('adj3') ?? 18750)) / 100000
  const x2 = (w * (adjustments?.get('adj4') ?? -16667)) / 100000
  const y3 = (h * (adjustments?.get('adj5') ?? 100000)) / 100000
  const x3 = (w * (adjustments?.get('adj6') ?? -16667)) / 100000
  const y4 = (h * (adjustments?.get('adj7') ?? 112963)) / 100000
  const x4 = (w * (adjustments?.get('adj8') ?? -8333)) / 100000
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: true },
    { d: `M${x1},0 L${x1},${h}`, fill: 'none', stroke: true },
    { d: `M${x1},${y1} L${x2},${y2} L${x3},${y3} L${x4},${y4}`, fill: 'none', stroke: true },
  ]
})

// Chart placeholders: frame + guide lines.
// PowerPoint uses these as pre-chart placeholders (chartX / chartPlus / chartStar).
multiPathPresets.set('chartx', (w, h) => {
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M0,0 L${w},${h} M${w},0 L0,${h}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('chartplus', (w, h) => {
  const cx = w / 2
  const cy = h / 2
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    { d: `M${cx},0 L${cx},${h} M0,${cy} L${w},${cy}`, fill: 'none', stroke: true },
  ]
})

multiPathPresets.set('chartstar', (w, h) => {
  // OOXML: 3 guide paths — 2 diagonals + 1 vertical (no horizontal center line)
  const cx = w / 2
  return [
    { d: `M0,0 L${w},0 L${w},${h} L0,${h} Z`, fill: 'norm', stroke: false },
    {
      d: `M0,0 L${w},${h} M${w},0 L0,${h} M${cx},0 L${cx},${h}`,
      fill: 'none',
      stroke: true,
    },
  ]
})

/**
 * Helper: compute OOXML arcTo endpoint and SVG arc command from current position.
 * OOXML arcTo: center = curPos - radius*dir(stAng), endpoint = center + radius*dir(stAng+swAng)
 * Returns { svgArc, endX, endY }
 */
function ooArcTo(
  curX: number,
  curY: number,
  wR: number,
  hR: number,
  stAngDeg: number,
  swAngDeg: number
): { svg: string; x: number; y: number } {
  const stRad = (stAngDeg * Math.PI) / 180
  const cx = curX - wR * Math.cos(stRad)
  const cy = curY - hR * Math.sin(stRad)
  const endRad = ((stAngDeg + swAngDeg) * Math.PI) / 180
  const ex = cx + wR * Math.cos(endRad)
  const ey = cy + hR * Math.sin(endRad)
  const absSweep = Math.abs(swAngDeg)
  const largeArc = absSweep > 180 ? 1 : 0
  const sweepFlag = swAngDeg >= 0 ? 1 : 0
  return { svg: `A${wR},${hR} 0 ${largeArc},${sweepFlag} ${ex},${ey}`, x: ex, y: ey }
}

// --- ribbon (OOXML spec: 3 paths with arcTo, adj1=16667, adj2=50000) ---
// Ribbon with tails at top, front panel at bottom. Three paths: body, darkenLess folds, outline.
multiPathPresets.set('ribbon', (w, h, adjustments) => {
  const adj1Raw = adjustments?.get('adj1') ?? 16667
  const adj2Raw = adjustments?.get('adj2') ?? 50000
  const a1 = Math.min(Math.max(adj1Raw, 0), 33333)
  const a2 = Math.min(Math.max(adj2Raw, 25000), 75000)

  const hc = w / 2
  const wd8 = w / 8
  const wd32 = w / 32
  const x10 = w - wd8
  const dx2 = (w * a2) / 200000
  const x2 = hc - dx2
  const x9 = hc + dx2
  const x3 = x2 + wd32
  const x8 = x9 - wd32
  const x5 = x2 + wd8
  const x6 = x9 - wd8
  const x4 = x5 - wd32
  const x7 = x6 + wd32
  const y1 = (h * a1) / 200000
  const y2 = (h * a1) / 100000
  const y4 = h - y2
  const y3 = y4 / 2
  const hR = (h * a1) / 400000
  const y5 = h - hR
  const y6 = y2 - hR

  let cx: number
  let cy: number
  let arc

  // Path 1: body fill (stroke=false)
  const p1: string[] = []
  cx = 0
  cy = 0
  p1.push(`M${0},${0}`)
  p1.push(`L${x4},${0}`)
  cx = x4
  cy = 0
  arc = ooArcTo(cx, cy, wd32, hR, 270, 180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x3},${y1}`)
  cx = x3
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 270, -180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x8},${y2}`)
  cx = x8
  cy = y2
  arc = ooArcTo(cx, cy, wd32, hR, 90, -180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x7},${y1}`)
  cx = x7
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 90, 180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${w},${0}`)
  p1.push(`L${x10},${y3}`)
  p1.push(`L${w},${y4}`)
  p1.push(`L${x9},${y4}`)
  p1.push(`L${x9},${y5}`)
  cx = x9
  cy = y5
  arc = ooArcTo(cx, cy, wd32, hR, 0, 90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x3},${h}`)
  cx = x3
  cy = h
  arc = ooArcTo(cx, cy, wd32, hR, 90, 90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x2},${y4}`)
  p1.push(`L${0},${y4}`)
  p1.push(`L${wd8},${y3}`)
  p1.push('Z')

  // Path 2: darkenLess folds (stroke=false)
  const p2: string[] = []
  // Left fold
  cx = x5
  cy = hR
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, wd32, hR, 0, 90)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x3},${y1}`)
  cx = x3
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 270, -180)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x5},${y2}`)
  p2.push('Z')
  // Right fold
  cx = x6
  cy = hR
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, wd32, hR, 180, -90)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x8},${y1}`)
  cx = x8
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 270, 180)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x6},${y2}`)
  p2.push('Z')

  // Path 3: outline (fill=none, includes fold lines)
  const p3: string[] = []
  cx = 0
  cy = 0
  p3.push(`M${0},${0}`)
  p3.push(`L${x4},${0}`)
  cx = x4
  cy = 0
  arc = ooArcTo(cx, cy, wd32, hR, 270, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x3},${y1}`)
  cx = x3
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 270, -180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x8},${y2}`)
  cx = x8
  cy = y2
  arc = ooArcTo(cx, cy, wd32, hR, 90, -180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x7},${y1}`)
  cx = x7
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 90, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${w},${0}`)
  p3.push(`L${x10},${y3}`)
  p3.push(`L${w},${y4}`)
  p3.push(`L${x9},${y4}`)
  p3.push(`L${x9},${y5}`)
  cx = x9
  cy = y5
  arc = ooArcTo(cx, cy, wd32, hR, 0, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x3},${h}`)
  cx = x3
  cy = h
  arc = ooArcTo(cx, cy, wd32, hR, 90, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x2},${y4}`)
  p3.push(`L${0},${y4}`)
  p3.push(`L${wd8},${y3}`)
  p3.push('Z')
  // Fold lines
  p3.push(`M${x5},${hR} L${x5},${y2}`)
  p3.push(`M${x6},${y2} L${x6},${hR}`)
  p3.push(`M${x2},${y4} L${x2},${y6}`)
  p3.push(`M${x9},${y6} L${x9},${y4}`)

  return [
    { d: p1.join(' '), fill: 'norm', stroke: false },
    { d: p2.join(' '), fill: 'darkenLess', stroke: false },
    { d: p3.join(' '), fill: 'none', stroke: true },
  ]
})

// --- ribbon2 (OOXML spec: 3 paths, inverted ribbon with tails at bottom) ---
multiPathPresets.set('ribbon2', (w, h, adjustments) => {
  const adj1Raw = adjustments?.get('adj1') ?? 16667
  const adj2Raw = adjustments?.get('adj2') ?? 50000
  const a1 = Math.min(Math.max(adj1Raw, 0), 33333)
  const a2 = Math.min(Math.max(adj2Raw, 25000), 75000)

  const hc = w / 2
  const wd8 = w / 8
  const wd32 = w / 32
  const x10 = w - wd8
  const dx2 = (w * a2) / 200000
  const x2 = hc - dx2
  const x9 = hc + dx2
  const x3 = x2 + wd32
  const x8 = x9 - wd32
  const x5 = x2 + wd8
  const x6 = x9 - wd8
  const x4 = x5 - wd32
  const x7 = x6 + wd32
  const dy1 = (h * a1) / 200000
  const y1 = h - dy1
  const dy2 = (h * a1) / 100000
  const y2 = h - dy2
  const y4 = dy2
  const y3 = (y4 + h) / 2
  const hR = (h * a1) / 400000
  const y6 = h - hR
  const y7 = y1 - hR

  let cx: number
  let cy: number
  let arc

  // Path 1: body fill (stroke=false)
  const p1: string[] = []
  p1.push(`M${0},${h}`)
  p1.push(`L${x4},${h}`)
  cx = x4
  cy = h
  arc = ooArcTo(cx, cy, wd32, hR, 90, -180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x3},${y1}`)
  cx = x3
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 90, 180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x8},${y2}`)
  cx = x8
  cy = y2
  arc = ooArcTo(cx, cy, wd32, hR, 270, 180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x7},${y1}`)
  cx = x7
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 270, -180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${w},${h}`)
  p1.push(`L${x10},${y3}`)
  p1.push(`L${w},${y4}`)
  p1.push(`L${x9},${y4}`)
  p1.push(`L${x9},${hR}`)
  cx = x9
  cy = hR
  arc = ooArcTo(cx, cy, wd32, hR, 0, -90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x3},${0}`)
  cx = x3
  cy = 0
  arc = ooArcTo(cx, cy, wd32, hR, 270, -90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x2},${y4}`)
  p1.push(`L${0},${y4}`)
  p1.push(`L${wd8},${y3}`)
  p1.push('Z')

  // Path 2: darkenLess folds (stroke=false)
  const p2: string[] = []
  // Left fold
  cx = x5
  cy = y6
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, wd32, hR, 0, -90)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x3},${y1}`)
  cx = x3
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 90, 180)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x5},${y2}`)
  p2.push('Z')
  // Right fold
  cx = x6
  cy = y6
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, wd32, hR, 180, 90)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x8},${y1}`)
  cx = x8
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 90, -180)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p2.push(`L${x6},${y2}`)
  p2.push('Z')

  // Path 3: outline (fill=none)
  const p3: string[] = []
  p3.push(`M${0},${h}`)
  p3.push(`L${wd8},${y3}`)
  p3.push(`L${0},${y4}`)
  p3.push(`L${x2},${y4}`)
  p3.push(`L${x2},${hR}`)
  cx = x2
  cy = hR
  arc = ooArcTo(cx, cy, wd32, hR, 180, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x8},${0}`)
  cx = x8
  cy = 0
  arc = ooArcTo(cx, cy, wd32, hR, 270, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x9},${y4}`)
  p3.push(`L${w},${y4}`)
  p3.push(`L${x10},${y3}`)
  p3.push(`L${w},${h}`)
  p3.push(`L${x7},${h}`)
  cx = x7
  cy = h
  arc = ooArcTo(cx, cy, wd32, hR, 90, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x8},${y1}`)
  cx = x8
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 90, -180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x3},${y2}`)
  cx = x3
  cy = y2
  arc = ooArcTo(cx, cy, wd32, hR, 270, -180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x4},${y1}`)
  cx = x4
  cy = y1
  arc = ooArcTo(cx, cy, wd32, hR, 270, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push('Z')
  // Fold lines
  p3.push(`M${x5},${y2} L${x5},${y6}`)
  p3.push(`M${x6},${y6} L${x6},${y2}`)
  p3.push(`M${x2},${y7} L${x2},${y4}`)
  p3.push(`M${x9},${y4} L${x9},${y7}`)

  return [
    { d: p1.join(' '), fill: 'norm', stroke: false },
    { d: p2.join(' '), fill: 'darkenLess', stroke: false },
    { d: p3.join(' '), fill: 'none', stroke: true },
  ]
})

// --- horizontalScroll (OOXML spec: 3 paths with arcTo) ---
multiPathPresets.set('horizontalscroll', (w, h, adjustments) => {
  const adjVal = adjustments?.get('adj') ?? 12500
  const a = Math.min(Math.max(adjVal, 0), 25000)
  const ss = Math.min(w, h)
  const ch = (ss * a) / 100000
  const ch2 = ch / 2
  const ch4 = ch / 4

  const y3 = ch + ch2
  const y4 = ch + ch
  const y6 = h - ch
  const y7 = h - ch2
  const y5 = y6 - ch2
  const x3 = w - ch
  const x4 = w - ch2

  // Path 1: main fill (stroke=false)
  const p1: string[] = []
  let cx: number
  let cy: number
  // moveTo (r, ch2) = (w, ch2)
  cx = w
  cy = ch2
  p1.push(`M${cx},${cy}`)
  // arcTo wR=ch2 hR=ch2 stAng=0 swAng=cd4(90°)
  let arc = ooArcTo(cx, cy, ch2, ch2, 0, 90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  // lnTo (x4, ch2) — but after the arc we should be at (x4, 0)… wait
  // Actually: arcTo from (w, ch2) with stAng=0 swAng=90° → center=(w-ch2, ch2), end=(w-ch2, 0)=x4,0
  // Then lnTo (x4, ch2)... hmm, this goes from top-right curl area
  // Let me re-read: lnTo pt x="x4" y="ch2"... that doesn't match. Wait, the lnTo goes DOWN.
  // After arc: we're at (x4, 0). lnTo (x4, ch2):
  p1.push(`L${x4},${ch2}`)
  // arcTo wR=ch4 hR=ch4 stAng=0 swAng=cd2(180°)
  arc = ooArcTo(x4, ch2, ch4, ch4, 0, 180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  // lnTo (x3, ch)
  p1.push(`L${x3},${ch}`)
  // lnTo (ch2, ch)
  p1.push(`L${ch2},${ch}`)
  // arcTo wR=ch2 hR=ch2 stAng=3cd4(270°) swAng=-5400000(-90°)
  cx = ch2
  cy = ch
  arc = ooArcTo(cx, cy, ch2, ch2, 270, -90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  // lnTo (0, y7)
  p1.push(`L${0},${y7}`)
  // arcTo wR=ch2 hR=ch2 stAng=cd2(180°) swAng=-10800000(-180°)
  cx = 0
  cy = y7
  arc = ooArcTo(cx, cy, ch2, ch2, 180, -180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  // lnTo (ch, y6)
  p1.push(`L${ch},${y6}`)
  // lnTo (x4, y6)
  p1.push(`L${x4},${y6}`)
  // arcTo wR=ch2 hR=ch2 stAng=cd4(90°) swAng=-5400000(-90°)
  cx = x4
  cy = y6
  arc = ooArcTo(cx, cy, ch2, ch2, 90, -90)
  p1.push(arc.svg)
  p1.push('Z')

  // Sub-path 2 in Path 1: left bottom curl circle
  cx = ch2
  cy = y4
  p1.push(`M${cx},${cy}`)
  // arcTo wR=ch2 hR=ch2 stAng=cd4(90°) swAng=-5400000(-90°)
  arc = ooArcTo(cx, cy, ch2, ch2, 90, -90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  // arcTo wR=ch4 hR=ch4 stAng=0 swAng=-10800000(-180°)
  arc = ooArcTo(cx, cy, ch4, ch4, 0, -180)
  p1.push(arc.svg)
  p1.push('Z')

  // Path 2: darkenLess fill (stroke=false) — shadow areas
  const p2: string[] = []
  // Sub-path 1: same as path1 sub-path2 (left bottom curl)
  cx = ch2
  cy = y4
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, ch2, ch2, 90, -90)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  arc = ooArcTo(cx, cy, ch4, ch4, 0, -180)
  p2.push(arc.svg)
  p2.push('Z')
  // Sub-path 2: right top curl
  cx = x4
  cy = ch
  p2.push(`M${cx},${cy}`)
  // arcTo wR=ch2 hR=ch2 stAng=cd4(90°) swAng=-16200000(-270°)
  arc = ooArcTo(cx, cy, ch2, ch2, 90, -270)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  // arcTo wR=ch4 hR=ch4 stAng=cd2(180°) swAng=-10800000(-180°)
  arc = ooArcTo(cx, cy, ch4, ch4, 180, -180)
  p2.push(arc.svg)
  p2.push('Z')

  // Path 3: stroke-only detail lines (fill=none)
  const p3: string[] = []
  // Sub-path 1: left side detail
  cx = 0
  cy = y3
  p3.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, ch2, ch2, 180, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x3},${ch}`)
  p3.push(`L${x3},${ch2}`)
  cx = x3
  cy = ch2
  arc = ooArcTo(cx, cy, ch2, ch2, 180, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${w},${y5}`)
  cx = w
  cy = y5
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${ch},${y6}`)
  p3.push(`L${ch},${y7}`)
  cx = ch
  cy = y7
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 180)
  p3.push(arc.svg)
  p3.push('Z')

  // Sub-path 2: top-right connector
  p3.push(`M${x3},${ch}`)
  p3.push(`L${x4},${ch}`)
  cx = x4
  cy = ch
  arc = ooArcTo(cx, cy, ch2, ch2, 90, -90)
  p3.push(arc.svg)

  // Sub-path 3: right curl inner detail
  p3.push(`M${x4},${ch}`)
  p3.push(`L${x4},${ch2}`)
  cx = x4
  cy = ch2
  arc = ooArcTo(cx, cy, ch4, ch4, 0, 180)
  p3.push(arc.svg)

  // Sub-path 4: left curl inner detail
  p3.push(`M${ch2},${y4}`)
  p3.push(`L${ch2},${y3}`)
  cx = ch2
  cy = y3
  arc = ooArcTo(cx, cy, ch4, ch4, 180, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 180)
  p3.push(arc.svg)

  // Sub-path 5: vertical divider
  p3.push(`M${ch},${y3}`)
  p3.push(`L${ch},${y6}`)

  return [
    { d: p1.join(' '), fill: 'norm', stroke: false },
    { d: p2.join(' '), fill: 'darkenLess', stroke: false },
    { d: p3.join(' '), fill: 'none', stroke: true },
  ]
})

// --- verticalScroll (OOXML spec: 3 paths with arcTo) ---
multiPathPresets.set('verticalscroll', (w, h, adjustments) => {
  const adjVal = adjustments?.get('adj') ?? 12500
  const a = Math.min(Math.max(adjVal, 0), 25000)
  const ss = Math.min(w, h)
  const ch = (ss * a) / 100000
  const ch2 = ch / 2
  const ch4 = ch / 4

  const x3 = ch + ch2
  const x4 = ch + ch
  const x6 = w - ch
  const x7 = w - ch2
  const _x5 = x6 - ch2
  const y3 = h - ch
  const y4 = h - ch2

  // Path 1: main fill (stroke=false)
  const p1: string[] = []
  let cx: number
  let cy: number
  cx = ch2
  cy = h
  p1.push(`M${cx},${cy}`)
  let arc = ooArcTo(cx, cy, ch2, ch2, 90, -90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${ch2},${y4}`)
  cx = ch2
  cy = y4
  arc = ooArcTo(cx, cy, ch4, ch4, 90, -180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${ch},${y3}`)
  p1.push(`L${ch},${ch2}`)
  cx = ch
  cy = ch2
  arc = ooArcTo(cx, cy, ch2, ch2, 180, 90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x7},${0}`)
  cx = x7
  cy = 0
  arc = ooArcTo(cx, cy, ch2, ch2, 270, 180)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p1.push(`L${x6},${ch}`)
  p1.push(`L${x6},${y4}`)
  cx = x6
  cy = y4
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 90)
  p1.push(arc.svg)
  p1.push('Z')

  // Sub-path 2: top-right curl circle
  cx = x4
  cy = ch2
  p1.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 90)
  p1.push(arc.svg)
  cx = arc.x
  cy = arc.y
  arc = ooArcTo(cx, cy, ch4, ch4, 90, 180)
  p1.push(arc.svg)
  p1.push('Z')

  // Path 2: darkenLess fill (stroke=false)
  const p2: string[] = []
  cx = x4
  cy = ch2
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 90)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  arc = ooArcTo(cx, cy, ch4, ch4, 90, 180)
  p2.push(arc.svg)
  p2.push('Z')

  cx = ch
  cy = y4
  p2.push(`M${cx},${cy}`)
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 270)
  p2.push(arc.svg)
  cx = arc.x
  cy = arc.y
  arc = ooArcTo(cx, cy, ch4, ch4, 270, 180)
  p2.push(arc.svg)
  p2.push('Z')

  // Path 3: stroke-only detail lines (fill=none)
  const p3: string[] = []
  cx = ch
  cy = y3
  p3.push(`M${cx},${cy}`)
  p3.push(`L${ch},${ch2}`)
  cx = ch
  cy = ch2
  arc = ooArcTo(cx, cy, ch2, ch2, 180, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x7},${0}`)
  cx = x7
  cy = 0
  arc = ooArcTo(cx, cy, ch2, ch2, 270, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x6},${ch}`)
  p3.push(`L${x6},${y4}`)
  cx = x6
  cy = y4
  arc = ooArcTo(cx, cy, ch2, ch2, 0, 90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${ch2},${h}`)
  cx = ch2
  cy = h
  arc = ooArcTo(cx, cy, ch2, ch2, 90, 180)
  p3.push(arc.svg)
  p3.push('Z')

  // top curl
  p3.push(`M${x3},${0}`)
  cx = x3
  cy = 0
  arc = ooArcTo(cx, cy, ch2, ch2, 270, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  arc = ooArcTo(cx, cy, ch4, ch4, 90, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${x4},${ch2}`)

  // horizontal divider
  p3.push(`M${x6},${ch}`)
  p3.push(`L${x3},${ch}`)

  // bottom-left curl detail
  p3.push(`M${ch2},${y3}`)
  cx = ch2
  cy = y3
  arc = ooArcTo(cx, cy, ch4, ch4, 270, 180)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${ch},${y4}`)

  // bottom curl
  p3.push(`M${ch2},${h}`)
  cx = ch2
  cy = h
  arc = ooArcTo(cx, cy, ch2, ch2, 90, -90)
  p3.push(arc.svg)
  cx = arc.x
  cy = arc.y
  p3.push(`L${ch},${y3}`)

  return [
    { d: p1.join(' '), fill: 'norm', stroke: false },
    { d: p2.join(' '), fill: 'darkenLess', stroke: false },
    { d: p3.join(' '), fill: 'none', stroke: true },
  ]
})

/**
 * Get multi-path preset sub-paths for a shape type.
 * Returns null if the shape is not a multi-path preset (use getPresetShapePath instead).
 */
export function getMultiPathPreset(
  shapeType: string,
  w: number,
  h: number,
  adjustments?: Map<string, number>
): PresetSubPath[] | null {
  const key = shapeType.toLowerCase()
  const gen = multiPathPresets.get(key) ?? multiPathPresets.get(shapeType)
  return gen ? gen(w, h, adjustments) : null
}

export function getPresetShapePath(
  shapeType: string,
  w: number,
  h: number,
  adjustments?: Map<string, number>
): string {
  // <a:prstGeom prst="textNoShape"> means text-only shape without geometry.
  if (shapeType === 'textNoShape' || shapeType.toLowerCase() === 'textnoshape') return ''
  // OOXML preset names are often camelCase; normalize to lowercase for lookup
  const key = shapeType.toLowerCase()
  const generator = presetShapes.get(key) ?? presetShapes.get(shapeType)
  if (generator) {
    return generator(w, h, adjustments)
  }
  // Fallback: simple rectangle
  logger.warn('Unknown preset shape, falling back to rectangle', { shapeType })
  return `M0,0 L${w},0 L${w},${h} L0,${h} Z`
}
