///|
fn trim_ascii_spaces(text : String) -> String {
  let mut start = 0
  let mut end = text.length()
  while start < end &&
        (
          text[start] == ' ' ||
          text[start] == '\t' ||
          text[start] == '\n' ||
          text[start] == '\r'
        ) {
    start = start + 1
  }
  while end > start &&
        (
          text[end - 1] == ' ' ||
          text[end - 1] == '\t' ||
          text[end - 1] == '\n' ||
          text[end - 1] == '\r'
        ) {
    end = end - 1
  }
  text.unsafe_substring(start~, end~)
}

///|
fn html_data_attributes(
  value : String,
) -> Map[String, String] raise ParseFailure {
  let attributes : Map[String, String] = Map([])
  let mut start = 0
  for index = 0; index <= value.length(); index = index + 1 {
    if index == value.length() || value[index] == ',' {
      let item = value.unsafe_substring(start~, end=index)
      let mut equals = -1
      for offset = 0; offset < item.length(); offset = offset + 1 {
        if item[offset] == '=' && equals < 0 {
          equals = offset
        }
      }
      guard equals >= 0 else {
        raise InvalidArgument(
          message="\\htmlData key/value '" + item + "' missing equals sign",
          loc=None,
        )
      }
      let key = trim_ascii_spaces(item.unsafe_substring(start=0, end=equals))
      let data_value = item.unsafe_substring(
        start=equals + 1,
        end=item.length(),
      )
      attributes["data-" + key] = data_value
      start = index + 1
    }
  }
  attributes
}

///|
fn html_spec() -> FunctionSpec {
  FunctionSpec::make(
    ["\\htmlClass", "\\htmlId", "\\htmlStyle", "\\htmlData"],
    2,
    arg_types=[RawArg, OriginalArg],
    allowed_in_text=true,
    handler=html_handler,
  )
}

///|
fn html_handler(
  context : FunctionContext,
  args : Array[ParseNode],
  _ : Array[ParseNode?],
) -> ParseNode raise ParseFailure {
  guard require_function_arg(args, 0, context.func_name)
    is Raw(string=value, ..) else {
    raise InternalInvariant(
      message="Expected raw argument for " + context.func_name,
    )
  }
  (context.report_nonstrict)(
    "htmlExtension", "HTML extension is disabled on strict mode",
  )
  let attributes : Map[String, String] = Map([])
  let trust_context = match context.func_name {
    "\\htmlClass" => {
      attributes["class"] = value
      HtmlClass(class=value)
    }
    "\\htmlId" => {
      attributes["id"] = value
      HtmlId(id=value)
    }
    "\\htmlStyle" => {
      attributes["style"] = value
      HtmlStyle(style=value)
    }
    "\\htmlData" => {
      let parsed = html_data_attributes(value)
      for key, item in parsed {
        attributes[key] = item
      }
      HtmlData(attributes~)
    }
    _ => raise InternalInvariant(message="Unrecognized html command")
  }
  let body = ord_argument(require_function_arg(args, 1, context.func_name))
  if (context.is_trusted)(trust_context) {
    Html(mode=context.mode, attributes~, body~)
  } else {
    OrdGroup(mode=context.mode, loc=None, body~, semisimple=false)
  }
}