///|
fn attrs_to_string(attrs : Array[(String, String)]) -> String {
  fn aux(i : Int, acc : String) -> String {
    if i < attrs.length() {
      let (k, v) = attrs[i]
      aux(i + 1, acc + " " + k + "=\"" + v + "\"")
    } else {
      acc
    }
  }
  aux(0, "")
}

///|
fn tag(name : String, attrs : Array[(String, String)], body : String) -> String {
  "<" + name + attrs_to_string(attrs) + ">" + body + ""
}

///|
fn void_tag(name : String, attrs : Array[(String, String)]) -> String {
  "<" + name + attrs_to_string(attrs) + "/>"
}

///|
pub fn svg_open(width : Float, height : Float) -> String {
  ""
}

///|
pub fn svg_close() -> String {
  ""
}

///|
pub fn rect(
  x : Float,
  y : Float,
  w : Float,
  h : Float,
  fill : String,
) -> String {
  void_tag("rect", [
    ("x", x.to_string()),
    ("y", y.to_string()),
    ("width", w.to_string()),
    ("height", h.to_string()),
    ("fill", fill),
  ])
}

///|
pub fn text(
  x : Float,
  y : Float,
  content : String,
  font_size : Int,
  anchor : String,
) -> String {
  tag(
    "text",
    [
      ("x", x.to_string()),
      ("y", y.to_string()),
      ("font-size", font_size.to_string()),
      ("font-family", "sans-serif"),
      ("text-anchor", anchor),
    ],
    content,
  )
}

///|
pub fn line(
  x1 : Float,
  y1 : Float,
  x2 : Float,
  y2 : Float,
  stroke : String,
  stroke_width : Float,
) -> String {
  void_tag("line", [
    ("x1", x1.to_string()),
    ("y1", y1.to_string()),
    ("x2", x2.to_string()),
    ("y2", y2.to_string()),
    ("stroke", stroke),
    ("stroke-width", stroke_width.to_string()),
  ])
}

///|
pub fn polyline(
  points : Array[(Float, Float)],
  stroke : String,
  stroke_width : Float,
  fill : String,
) -> String {
  fn aux(i : Int, acc : String) -> String {
    if i < points.length() {
      let (x, y) = points[i]
      let sep = if i > 0 { " " } else { "" }
      aux(i + 1, acc + sep + x.to_string() + "," + y.to_string())
    } else {
      acc
    }
  }
  let pts = aux(0, "")
  void_tag("polyline", [
    ("points", pts),
    ("stroke", stroke),
    ("stroke-width", stroke_width.to_string()),
    ("fill", fill),
  ])
}

///|
fn build_point_string(
  points : Array[(Float, Float)],
  idx : Int,
  acc : String,
) -> String {
  if idx >= points.length() {
    acc
  } else {
    let (x, y) = points[idx]
    let new_acc = if idx == 0 {
      x.to_string() + "," + y.to_string()
    } else {
      acc + " " + x.to_string() + "," + y.to_string()
    }
    build_point_string(points, idx + 1, new_acc)
  }
}

///|
pub fn polygon(
  points : Array[(Float, Float)],
  fill : String,
  stroke : String,
  stroke_width : Float,
) -> String {
  let pts_str = build_point_string(points, 0, "")
  void_tag("polygon", [
    ("points", pts_str),
    ("fill", fill),
    ("stroke", stroke),
    ("stroke-width", stroke_width.to_string()),
  ])
}

///|
pub fn circle(cx : Float, cy : Float, r : Float, fill : String) -> String {
  void_tag("circle", [
    ("cx", cx.to_string()),
    ("cy", cy.to_string()),
    ("r", r.to_string()),
    ("fill", fill),
  ])
}

///|
pub fn path(d : String, fill : String, stroke : String) -> String {
  void_tag("path", [
    ("d", d),
    ("fill", fill),
    ("stroke", stroke),
    ("stroke-width", "1"),
  ])
}