///|
/// Proof of needle/travel geometry only; it does not simulate fabric or thread.
pub fn Plan::svg(self : Plan) -> String {
  let palette = [
    "#b91c1c", "#1d4ed8", "#15803d", "#a16207", "#7e22ce", "#0f766e", "#be185d",
    "#111827",
  ]
  let out : Array[String] = [
    "",
    "",
  ]
  let mut prev : Point = { x: 0, y: 0, }
  for c in self.moves {
    if c.kind == Stitch && c.position != prev {
      out.push(
        "",
      )
    } else if c.kind == Jump {
      out.push(
        "",
      )
    }
    prev = c.position
  }
  out.push("")
  out.join("")
}

///|
/// Stable machine-independent JSON command list; positions remain in 0.1 mm.
pub fn Plan::report(self : Plan) -> Json {
  let commands = self.moves.map(fn(c) {
    (
      {
        "x": Json(c.position.x),
        "y": Json(c.position.y),
        "kind": Json(kind_name(c.kind)),
        "thread": Json(c.thread),
      } : Json)
  })
  {
    "unit": "0.1mm",
    "hoop_width": Json(self.width),
    "hoop_height": Json(self.height),
    "max_step": Json(self.max_step),
    "stitches": Json(self.moves.filter(fn(c) { c.kind == Stitch }).length()),
    "jumps": Json(self.moves.filter(fn(c) { c.kind == Jump }).length()),
    "color_changes": Json(self.moves.filter(fn(c) { c.kind == Color }).length()),
    "commands": Json(commands),
  }
}