///|
pub struct Affix {
  prefix : String
  suffix : String
  delimiter : String
  text_case : String
  font_style : String
  font_weight : String
  vertical_align : String
}

///|
pub struct TextNode {
  variable : String
  value : String
  macro_name : String
  term : String
  form : String
}

///|
pub struct NameNode {
  variable : String
  form : String
  and_word : String
  delimiter : String
  initialize_with : String
  initialize : Bool
  name_as_sort_order : String
  delimiter_precedes_last : String
  et_al_min : Int
  et_al_use_first : Int
  label_variable : String
}

///|
pub struct NumberNode {
  variable : String
  form : String
  prefix : String
  suffix : String
  delimiter : String
  text_case : String
}

///|
pub struct DatePart {
  name : String
  form : String
  prefix : String
  suffix : String
  range_delimiter : String
  text_case : String
}

///|
pub struct DateNode {
  variable : String
  parts : Array[DatePart]
  delimiter : String
}

///|
pub enum Condition {
  Variable(String)
  Type(String)
  Numeric(String)
  IsUncertain(String)
  IsPlural(String)
  Position(String)
}

///|
pub struct ChooseBranch {
  conditions : Array[Condition]
  match_mode : String
  children : Array[Node]
}

///|
pub enum Node {
  Text(Affix, TextNode)
  Names(Affix, NameNode)
  Number(Affix, NumberNode)
  Date(Affix, DateNode)
  Group(Affix, Array[Node])
  Choose(Affix, Array[ChooseBranch])
}

///|
pub struct Layout {
  affix : Affix
  children : Array[Node]
}

///|
pub struct SortKey {
  variable : String
  macro_name : String
  names_min : Int
  names_use_first : Int
  sort_order : String
}

///|
pub struct Style {
  title : String
  version : String
  default_locale : String
  macros : Map[String, Array[Node]]
  citation : Layout?
  bibliography : Layout?
  citation_sort : Array[SortKey]
  bibliography_sort : Array[SortKey]
  citation_disambiguate : Bool
  citation_collapse : String
  citation_delimiter : String
}

///|
fn read_affix(el : XmlElem) -> Affix {
  {
    prefix: el.attr("prefix"),
    suffix: el.attr("suffix"),
    delimiter: el.attr("delimiter"),
    text_case: el.attr("text-case"),
    font_style: el.attr("font-style"),
    font_weight: el.attr("font-weight"),
    vertical_align: el.attr("vertical-align"),
  }
}

///|
fn parse_conditions(el : XmlElem) -> Array[Condition] {
  let out = []
  for variable in el.attr("variable").split(" ") {
    if !variable.is_empty() {
      out.push(Variable(variable.to_owned()))
    }
  }
  for typ in el.attr("type").split(" ") {
    if !typ.is_empty() {
      out.push(Type(typ.to_owned()))
    }
  }
  for value in el.attr("is-numeric").split(" ") {
    if !value.is_empty() {
      out.push(Numeric(value.to_owned()))
    }
  }
  for value in el.attr("is-uncertain-date").split(" ") {
    if !value.is_empty() {
      out.push(IsUncertain(value.to_owned()))
    }
  }
  for value in el.attr("is-plural").split(" ") {
    if !value.is_empty() {
      out.push(IsPlural(value.to_owned()))
    }
  }
  for value in el.attr("position").split(" ") {
    if !value.is_empty() {
      out.push(Position(value.to_owned()))
    }
  }
  out
}

///|
fn parse_node(el : XmlElem) -> Node? {
  let affix = read_affix(el)
  match el.name {
    "text" =>
      Some(
        Text(affix, {
          variable: el.attr("variable"),
          value: el.attr("value"),
          macro_name: el.attr("macro"),
          term: el.attr("term"),
          form: el.attr("form"),
        }),
      )
    "names" =>
      Some(
        Names(affix, {
          variable: el.attr("variable"),
          form: "long",
          and_word: "text",
          delimiter: ", ",
          initialize_with: ". ",
          initialize: false,
          name_as_sort_order: "",
          delimiter_precedes_last: "contextual",
          et_al_min: 0,
          et_al_use_first: 1,
          label_variable: "",
        }),
      )
    "number" =>
      Some(
        Number(affix, {
          variable: el.attr("variable"),
          form: el.attr("form"),
          prefix: "",
          suffix: "",
          delimiter: "",
          text_case: "",
        }),
      )
    "date" =>
      Some(
        Date(affix, {
          variable: el.attr("variable"),
          parts: parse_date_parts(el),
          delimiter: el.attr("delimiter"),
        }),
      )
    "group" => Some(Group(affix, parse_children(el)))
    "choose" => Some(Choose(affix, parse_choose(el)))
    _ => None
  }
}

///|
fn parse_date_parts(el : XmlElem) -> Array[DatePart] {
  let out = []
  for child in el.kids() {
    if child.name == "date-part" {
      out.push({
        name: child.attr("name"),
        form: child.attr("form"),
        prefix: child.attr("prefix"),
        suffix: child.attr("suffix"),
        range_delimiter: child.attr("range-delimiter"),
        text_case: child.attr("text-case"),
      })
    }
  }
  out
}

///|
fn parse_children(el : XmlElem) -> Array[Node] {
  let out = []
  for child in el.kids() {
    match parse_node(child) {
      Some(n) => out.push(n)
      None => ()
    }
  }
  out
}

///|
fn parse_choose(el : XmlElem) -> Array[ChooseBranch] {
  let out = []
  for child in el.kids() {
    if child.name == "if" || child.name == "else-if" || child.name == "else" {
      out.push({
        conditions: parse_conditions(child),
        match_mode: child.attr("match"),
        children: parse_children(child),
      })
    }
  }
  out
}

///|
fn parse_sort_keys(el : XmlElem?) -> Array[SortKey] {
  match el {
    None => []
    Some(s) => {
      let out = []
      for child in s.kids() {
        if child.name == "key" {
          let min = match parse_int(child.attr("names-min")) {
            Some(x) => x
            None => 0
          }
          let first = match parse_int(child.attr("names-use-first")) {
            Some(x) => x
            None => 1
          }
          out.push({
            variable: child.attr("variable"),
            macro_name: child.attr("macro"),
            names_min: min,
            names_use_first: first,
            sort_order: child.attr("sort"),
          })
        }
      }
      out
    }
  }
}

///|
fn parse_layout(el : XmlElem) -> Layout {
  { affix: read_affix(el), children: parse_children(el), }
}

///|
fn parse_style(style_xml : String) -> Result[Style, CslError] {
  let root = match parse_xml(style_xml) {
    Ok(x) => x
    Err(e) => return Err(e)
  }
  guard root.name == "style" else {
    return Err(BadStyle("root element must be style"))
  }
  let mut citation = None
  let mut bibliography = None
  let macros = Map([])
  let mut citation_sort = []
  let mut bibliography_sort = []
  let mut collapse = ""
  let mut delimiter = "; "
  for child in root.kids() {
    match child.name {
      "macro" => macros[child.attr("name")] = parse_children(child)
      "citation" => {
        citation_sort = parse_sort_keys(child.first("sort"))
        collapse = child.attr("collapse")
        match child.first("layout") {
          Some(x) => {
            citation = Some(parse_layout(x))
            delimiter = x.attr("delimiter")
          }
          None => ()
        }
      }
      "bibliography" => {
        bibliography_sort = parse_sort_keys(child.first("sort"))
        match child.first("layout") {
          Some(x) => bibliography = Some(parse_layout(x))
          None => ()
        }
      }
      _ => ()
    }
  }
  Ok({
    title: root.attr("title"),
    version: root.attr("version"),
    default_locale: root.attr("default-locale"),
    macros,
    citation,
    bibliography,
    citation_sort,
    bibliography_sort,
    citation_disambiguate: root.attr("disambiguate-add-year-suffix") == "true",
    citation_collapse: collapse,
    citation_delimiter: delimiter,
  })
}