///|
fn css_is_whitespace(c : Char) -> Bool {
  c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\u000C'
}

///|
fn split_class_names(value : String) -> Array[String] {
  let result : Array[String] = []
  let mut start = -1
  for i in 0..<=value.length() {
    let whitespace = i == value.length() ||
      css_is_whitespace(Int::unsafe_to_char(value[i].to_int()))
    if whitespace {
      if start >= 0 {
        result.push(build_substring(value, start, i))
        start = -1
      }
    } else if start < 0 {
      start = i
    }
  }
  result
}

///|
fn style_element(
  tag : String,
  attrs : Array[(String, String)],
  parent : @css_selector.Element?,
  previous_sibling : @css_selector.Element?,
  sibling_index : Int,
  sibling_count : Int,
) -> @css_selector.Element {
  let mut element = @css_selector.Element::new(tag.to_lower())
  for attr in attrs {
    element = element.set_attribute(attr.0, attr.1)
    if attr.0 == "id" {
      element = element.set_id(attr.1)
    } else if attr.0 == "class" {
      for class_name in split_class_names(attr.1) {
        element = element.add_class(class_name)
      }
    }
  }
  element = element.set_sibling_info(sibling_index, sibling_count)
  match previous_sibling {
    Some(previous) => element = { ..element, prev_sibling: Some(previous) }
    None => ()
  }
  match parent {
    Some(parent) => element.set_parent(parent)
    None => element
  }
}

///|
fn declarations_from_css_body(
  value : String,
) -> Array[@css_cascade.Declaration] {
  let stylesheet = @css.parse_stylesheet("* {" + value + "}")
  if stylesheet.rules.length() == 0 {
    []
  } else {
    stylesheet.rules[0].declarations
  }
}

///|
fn presentation_stylesheet(
  attrs : Array[(String, String)],
) -> @css_cascade.Stylesheet? {
  let declarations : Array[@css_cascade.Declaration] = []
  for attr in attrs {
    let name = attr.0
    if name == name.to_lower() && is_computed_style_property(name) {
      declarations.push(
        @css_cascade.Declaration::new(name, trim_string(attr.1)),
      )
    }
  }
  if declarations.length() == 0 {
    return None
  }
  let selector = match @css.parse_selector("*") {
    Some(selector) => selector
    None => return None
  }
  let stylesheet = @css_cascade.Stylesheet::new(Author)
  stylesheet.add_rule("*", selector, declarations)
  Some(stylesheet)
}

///|
fn inline_declarations(
  attrs : Array[(String, String)],
) -> Array[@css_cascade.Declaration] {
  match get_attr(attrs, "style") {
    Some(value) => declarations_from_css_body(value)
    None => []
  }
}

///|
fn parse_inline_style_declarations(value : String) -> Array[StyleDeclaration] {
  cascaded_style_declarations(
    @css.cascade_declarations(declarations_from_css_body(value)),
  )
}

///|
fn cascade_element_styles(
  stylesheets : Array[@css_cascade.Stylesheet],
  element : @css_selector.Element,
  attrs : Array[(String, String)],
  media_environment : @css_media.MediaEnvironment,
  forced_states? : @css_selector.ForcedPseudoStates = @css_selector.ForcedPseudoStates::none(),
) -> @css_cascade.CascadedValues {
  let author_stylesheets : Array[@css_cascade.Stylesheet] = []
  match presentation_stylesheet(attrs) {
    Some(stylesheet) => author_stylesheets.push(stylesheet)
    None => ()
  }
  for stylesheet in stylesheets {
    author_stylesheets.push(stylesheet)
  }
  @css.cascade_element_with_media_forced_and_validator(
    element,
    author_stylesheets,
    inline_declarations(attrs),
    Some(media_environment),
    forced_states,
    svg_declaration_is_valid,
  )
}

///|
fn ElementState::to_forced_pseudo_states(
  self : ElementState,
) -> @css_selector.ForcedPseudoStates {
  {
    hover: self.hover,
    focus: self.focus,
    active: self.active,
    focus_visible: self.focus_visible,
    focus_within: self.focus_within,
  }
}

///|
fn property_value_text(value : @css_cascade.PropertyValue) -> String {
  match value {
    Value(value) => value
    Inherit => "inherit"
    Initial => "initial"
    Unset => "unset"
    Revert => "revert"
    RevertLayer => "revert-layer"
  }
}

///|
fn cascaded_style_declarations(
  values : @css_cascade.CascadedValues,
) -> Array[StyleDeclaration] {
  let declarations : Array[StyleDeclaration] = []
  values.each(fn(name, declaration) {
    declarations.push({ name, value: property_value_text(declaration.value) })
  })
  declarations
}