///|
/// Reads an integer attribute, returning `default` when absent and raising
/// `InvalidXml` (mirroring the attribute name) when present but unparseable.
fn attr_int_or(
  tag : StringView,
  name : StringView,
  default : Int,
) -> Int raise XlsxError {
  match attr_value(tag, name) {
    Some(value) =>
      @string.parse_int(value, base=10) catch {
        _ => raise InvalidXml(msg="\{name} invalid")
      }
    None => default
  }
}

///|
fn parse_num_fmts(xml : StringView) -> Map[Int, String] raise XlsxError {
  let num_fmts : Map[Int, String] = Map([])
  let body = match extract_tag_body(xml, "numFmts") {
    Some(value) => value
    None => return num_fmts
  }
  let mut first = true
  for chunk in body.split("") {
      Some(pos) => pos
      None =>
        match text.find(">") {
          Some(pos) => pos
          None => raise InvalidXml(msg="numFmt tag not closed")
        }
    }
    let tag = text[:end]
    let id_text = match attr_value(tag, "numFmtId") {
      Some(value) => value
      None => continue
    }
    let fmt_text = match attr_value(tag, "formatCode") {
      Some(value) => value
      None => continue
    }
    let id = @string.parse_int(id_text, base=10) catch {
      _ => raise InvalidXml(msg="numFmtId invalid")
    }
    num_fmts[id] = unescape_xml_text(fmt_text)
  }
  num_fmts
}

///|
fn parse_cell_xfs(
  xml : StringView,
  num_fmts : Map[Int, String],
  fonts : ArrayView[Font],
  fills : ArrayView[Fill],
  borders : ArrayView[Array[Border]],
) -> Array[Style] raise XlsxError {
  let styles : Array[Style] = []
  let body = match extract_tag_body(xml, "cellXfs") {
    Some(value) => value
    None => {
      styles.push(Style::new())
      return styles
    }
  }
  let mut first = true
  for chunk in body.split("") {
      Some(pos) => pos
      None => raise InvalidXml(msg="xf tag not closed")
    }
    let open_tag = text[:open_end]
    let self_closing = open_tag.has_suffix("/")
    let tag = if self_closing {
      open_tag[:open_tag.length() - 1]
    } else {
      open_tag
    }
    let xf_body = if self_closing {
      ""
    } else {
      let rest = text[open_end + 1:]
      let close = match rest.find("") {
        Some(pos) => pos
        None => raise InvalidXml(msg="xf close missing")
      }
      rest[:close].to_owned()
    }
    let num_fmt_id = attr_int_or(tag, "numFmtId", 0)
    let mut style = match num_fmts.get(num_fmt_id) {
      Some(code) => Style::number_format(code)
      None =>
        if num_fmt_id == 0 {
          Style::new()
        } else {
          Style::builtin_number_format(num_fmt_id)
        }
    }
    let font_id = attr_int_or(tag, "fontId", 0)
    if font_id > 0 && font_id < fonts.length() {
      style = style.with_font(fonts[font_id])
    }
    let fill_id = attr_int_or(tag, "fillId", 0)
    if fill_id > 1 && fill_id < fills.length() {
      style = style.with_fill(fills[fill_id])
    }
    let border_id = attr_int_or(tag, "borderId", 0)
    if border_id > 0 && border_id < borders.length() {
      let border = borders[border_id]
      if border.length() > 0 {
        style = style.with_border(border)
      }
    }
    if !self_closing {
      match parse_protection_entry(xf_body) {
        Some(protection) => style = style.with_protection(protection)
        None => ()
      }
      match parse_alignment_entry(xf_body) {
        Some(alignment) => style = style.with_alignment(alignment)
        None => ()
      }
    }
    styles.push(style)
  }
  if styles.length() == 0 {
    styles.push(Style::new())
  }
  styles
}

///|
fn parse_dxfs(
  xml : StringView,
  num_fmts : Map[Int, String],
  fonts : ArrayView[Font],
) -> Array[Style] raise XlsxError {
  let styles : Array[Style] = []
  let xml_str = xml.to_owned()
  let open_tag = " pos
    None => return styles
  }
  let after_start = xml_str[start:]
  let tag_end = match after_start.find(">") {
    Some(pos) => pos
    None => raise InvalidXml(msg="dxfs tag close missing")
  }
  let tag_text = after_start[:tag_end]
  if tag_text.has_suffix("/") {
    return styles
  }
  let close_tag = ""
  let close_start = match after_start.find(close_tag) {
    Some(pos) => pos
    None => raise InvalidXml(msg="dxfs close missing")
  }
  let body = after_start[tag_end + 1:close_start]
  let mut first = true
  for chunk in body.split("") {
      Some(pos) => pos
      None =>
        match text.find("/>") {
          Some(pos) => pos
          None => raise InvalidXml(msg="dxf tag not closed")
        }
    }
    let tag = text[:end]
    let mut style = match tag_attributes_in(tag, "numFmt") {
      Some(num_fmt_tag) => {
        let num_fmt_id = attr_int_or(num_fmt_tag, "numFmtId", 0)
        match attr_value(num_fmt_tag, "formatCode") {
          Some(value) => Style::number_format(unescape_xml_text(value))
          None =>
            match num_fmts.get(num_fmt_id) {
              Some(code) => Style::number_format(code)
              None =>
                if num_fmt_id == 0 {
                  Style::new()
                } else {
                  Style::builtin_number_format(num_fmt_id)
                }
            }
        }
      }
      None => Style::new()
    }
    match extract_tag_body(tag, "font") {
      Some(font_body) => {
        let font = parse_font_entry(font_body)
        // Only attach a non-default font. A default font in a dxf is typically redundant.
        if fonts.length() == 0 || font != fonts[0] {
          style = style.with_font(font)
        }
      }
      None => ()
    }
    match extract_tag_body(tag, "fill") {
      Some(fill_body) => {
        let fill = parse_fill_entry(fill_body)
        match fill.typ {
          Some(_) => style = style.with_fill(fill)
          None => ()
        }
      }
      None => ()
    }
    match extract_tag_body(tag, "border") {
      Some(border_body) => {
        let border_tag = match tag_attributes_in(tag, "border") {
          Some(value) => value
          None => "border"
        }
        let border = parse_border_entry(border_tag, border_body)
        if border.length() > 0 {
          style = style.with_border(border)
        }
      }
      None => ()
    }
    match parse_protection_entry(tag) {
      Some(protection) => style = style.with_protection(protection)
      None => ()
    }
    match parse_alignment_entry(tag) {
      Some(alignment) => style = style.with_alignment(alignment)
      None => ()
    }
    styles.push(style)
  }
  styles
}

///|
fn parse_styles(
  xml : StringView,
) -> (Array[Style], Array[Style]) raise XlsxError {
  let num_fmts = parse_num_fmts(xml)
  let fonts = parse_fonts(xml)
  let fills = parse_fills(xml)
  let borders = parse_borders(xml)
  let styles = parse_cell_xfs(xml, num_fmts, fonts, fills, borders)
  let conditional_styles = parse_dxfs(xml, num_fmts, fonts)
  (styles, conditional_styles)
}