///|
/// Exact authored or deterministically computed path. The limit is checked
/// before appending each segment; no partially returned path escapes.
pub fn bpmn_geometry_path(edge : @bpmn_layout.Connection, max_units? : Int = 16777216) -> String? {
  if max_units <= 0 { return None }
  let result=StringBuilder(); let mut units=0
  for i,p in edge.points {
    let prefix=if i == 0 { "M" } else { " L" }
    for part in [prefix,p.x.token," ",p.y.token] { if part.length() > max_units-units { return None }; units+=part.length(); result.write_string(part) }
  }
  Some(result.to_string())
}

///|
fn BpmnPaint::definitions(self : BpmnPaint) -> Array[@writer.Node] {
  let stroke=self.paint.color("arrow"); let bg=self.paint.color("bg")
  fn marker(id : String, parts : ArrayView[@writer.Node], orient : String) -> @writer.Node {
    self.element("marker",attrs=[("id",id),("viewBox","-12 -7 28 14"),("refX","0"),("refY","0"),("markerWidth","28"),("markerHeight","14"),("markerUnits","userSpaceOnUse"),("orient",orient)],children=parts)
  }
  [marker("sequence-head",[self.path("M-10 -5 L0 0 L-10 5 Z",stroke,fill=stroke)],"auto-start-reverse"),
   marker("open-head",[self.path("M-10 -5 L0 0 L-10 5",stroke)],"auto-start-reverse"),
   marker("message-tail",[self.circle("0","0","3.5",stroke,fill=bg)],"auto-start-reverse"),
   marker("condition-tail",[self.path("M0 0 L6 -4 L12 0 L6 4 Z",stroke,fill=bg)],"auto"),
   marker("default-tail",[self.path("M4 -5 L10 5",stroke)],"auto")]
}

///|
fn BpmnPaint::connection(self : BpmnPaint, edge : @bpmn_layout.Connection, source_kind : @bpmn.NodeKind?) -> @writer.Node {
  let flow=edge.flow; let stroke=self.paint.color("arrow")
  guard bpmn_geometry_path(edge,max_units=self.limit-self.used) is Some(d) else { self.fail("bpmn/render-limit","Connection path exceeds the output budget"); return self.element("g") }
  let attrs=self.metadata(flow.id,flow.identity,flow.properties)
  attrs.push(("id","flow-\{edge.index}")); attrs.push(("class","bpmn-flow")); attrs.push(("data-kind",bpmn_connection(flow.kind)))
  attrs.push(("data-from",edge.source)); attrs.push(("data-to",edge.target))
  if flow.condition is Some(condition) { attrs.push(("data-condition",condition)) }
  let line_attrs=[("d",d),("fill","none"),("stroke",stroke),("stroke-width","1.5"),("stroke-linejoin","round")]
  match flow.kind {
    SequenceFlow => {
      line_attrs.push(("marker-end","url(#sequence-head)"))
      if flow.is_default { line_attrs.push(("marker-start","url(#default-tail)")) }
      else if flow.condition != None && source_kind.map(@bpmn_layout.activity).unwrap_or(false) { line_attrs.push(("marker-start","url(#condition-tail)")) }
    }
    MessageFlow => { line_attrs.push(("stroke-dasharray","6 4")); line_attrs.push(("marker-start","url(#message-tail)")); line_attrs.push(("marker-end","url(#open-head)")) }
    Association => {
      line_attrs.push(("stroke-dasharray","1 4")); line_attrs.push(("stroke-linecap","round"))
      if flow.direction != None { line_attrs.push(("marker-end","url(#open-head)")) }
      if flow.direction == Both { line_attrs.push(("marker-start","url(#open-head)")) }
    }
    DataAssociation | DataInputAssociation | DataOutputAssociation => { line_attrs.push(("stroke-dasharray","1 4")); line_attrs.push(("stroke-linecap","round")); line_attrs.push(("marker-end","url(#open-head)")) }
  }
  let children=[self.element("path",attrs=line_attrs)]
  if flow.kind == MessageFlow && flow.geometry.bind(g => g.message_visible) is Some(visibility) {
    let point=@bpmn_layout.midpoint(edge.line).unwrap()
    children.push(self.element("g",attrs=[("transform","translate(\{bn(point.x-12.0)} \{bn(point.y-12.0)})")],children=self.event_symbol(Message,visibility == NonInitiating,stroke)))
  }
  self.element("g",attrs~,children~)
}