///|
#valtype
struct Html {
  raw : String
}

///|
pub trait WriteToHtmlBuilder {
  fn write_to(Self, HtmlBuilder) -> Unit
}

///|
#valtype
struct HtmlBuilder {
  buffer : StringBuilder
}

///|
pub fn Html::Html(raw~ : String) -> Html {
  { raw, }
}

///|
pub fn Html::escape(str : String) -> Html {
  Html(raw=html_escape(str))
}

///|
pub fn Html::build(f : (HtmlBuilder) -> Unit) -> Html {
  let b = HtmlBuilder()
  f(b)
  b.html()
}

///|
pub fn HtmlBuilder::HtmlBuilder() -> HtmlBuilder {
  { buffer: StringBuilder() }
}

///|
pub fn HtmlBuilder::write_string(self : HtmlBuilder, str : String) -> Unit {
  self.buffer.write_string(str)
}

///|
pub fn[T : WriteToHtmlBuilder] HtmlBuilder::write(
  self : HtmlBuilder,
  value : T,
) -> Unit {
  value.write_to(self)
}

///|
pub fn HtmlBuilder::html(self : HtmlBuilder) -> Html {
  Html(raw=self.buffer.to_string())
}

///|
pub impl WriteToHtmlBuilder for String with fn write_to(
  self : String,
  builder : HtmlBuilder,
) {
  builder.write_string(html_escape(self))
}

///|
pub impl WriteToHtmlBuilder for Html with fn write_to(
  self : Html,
  builder : HtmlBuilder,
) {
  builder.write_string(self.raw)
}

///|
fn html_escape(s : String) -> String {
  // TODO: Use lexmatch
  let b = StringBuilder()
  for ch in s {
    match ch {
      '&' => b.write_string("&")
      '<' => b.write_string("<")
      '>' => b.write_string(">")
      '"' => b.write_string(""")
      '\'' => b.write_string("'")
      _ => b.write_char(ch)
    }
  }
  b.to_string()
}