///|
fn parse_pattern_units(value : String) -> PatternUnits {
  match trim_string(value) {
    "userSpaceOnUse" => UserSpaceOnUse
    "objectBoundingBox" => ObjectBoundingBox
    _ => ObjectBoundingBox
  }
}

///|
fn parse_gradient_units(value : String) -> GradientUnits {
  match trim_string(value) {
    "userSpaceOnUse" => UserSpaceOnUse
    "objectBoundingBox" => ObjectBoundingBox
    _ => ObjectBoundingBox
  }
}

///|
fn parse_linear_gradient_from_attrs(
  attrs : Array[(String, String)],
  ctx : SVGParseContext,
  parent_tag : String,
) -> Unit {
  if !is_paint_server_context(parent_tag) {
    return
  }
  parse_linear_gradient_with_stops(attrs, [], ctx)
}

///|
fn parse_linear_gradient(
  parser : SVGParser,
  attrs : Array[(String, String)],
  ctx : SVGParseContext,
  tag_name : String,
  parent_tag : String,
  gradient_style : InheritedStyle,
) -> Unit {
  if !is_paint_server_context(parent_tag) {
    skip_to_end_tag(parser, tag_name)
    return
  }
  let stops = parse_gradient_stops(parser, tag_name, attrs, ctx, gradient_style)
  parse_linear_gradient_with_stops(attrs, stops, ctx)
}

///|
fn parse_linear_gradient_with_stops(
  attrs : Array[(String, String)],
  stops : Array[GradientStop],
  ctx : SVGParseContext,
) -> Unit {
  let id = match get_attr(attrs, "id") {
    Some(v) => v
    None => ""
  }
  if id.length() == 0 {
    return
  }
  let reference = match get_href_attr(attrs) {
    Some(v) => parse_url_ref(v)
    None => None
  }
  let x1 = parse_gradient_coord(get_attr(attrs, "x1"), 0.0)
  let y1 = parse_gradient_coord(get_attr(attrs, "y1"), 0.0)
  let x2 = parse_gradient_coord(get_attr(attrs, "x2"), 1.0)
  let y2 = parse_gradient_coord(get_attr(attrs, "y2"), 0.0)
  let spread_method = match get_attr(attrs, "spreadMethod") {
    Some(v) => parse_spread_method(v)
    None => Pad
  }
  let units = match get_attr(attrs, "gradientUnits") {
    Some(v) => parse_gradient_units(v)
    None => ObjectBoundingBox
  }
  let transform = match get_attr(attrs, "gradientTransform") {
    Some(v) => parse_transform(v)
    None => Transform::identity()
  }
  let grad = {
    ..LinearGradient::new(x1, y1, x2, y2, stops),
    spread_method,
    units,
    transform,
  }
  let mut specified = 0
  if get_attr(attrs, "x1") is Some(_) {
    specified = specified | 1
  }
  if get_attr(attrs, "y1") is Some(_) {
    specified = specified | 2
  }
  if get_attr(attrs, "x2") is Some(_) {
    specified = specified | 4
  }
  if get_attr(attrs, "y2") is Some(_) {
    specified = specified | 8
  }
  if get_attr(attrs, "spreadMethod") is Some(_) {
    specified = specified | 16
  }
  if get_attr(attrs, "gradientUnits") is Some(_) {
    specified = specified | 32
  }
  if get_attr(attrs, "gradientTransform") is Some(_) {
    specified = specified | 64
  }
  if !stops.is_empty() {
    specified = specified | 128
  }
  ctx.gradients.add_definition(id, Linear(grad), reference, specified)
}

///|
fn parse_radial_gradient_from_attrs(
  attrs : Array[(String, String)],
  ctx : SVGParseContext,
  parent_tag : String,
) -> Unit {
  if !is_paint_server_context(parent_tag) {
    return
  }
  parse_radial_gradient_with_stops(attrs, [], ctx)
}

///|
fn parse_radial_gradient(
  parser : SVGParser,
  attrs : Array[(String, String)],
  ctx : SVGParseContext,
  tag_name : String,
  parent_tag : String,
  gradient_style : InheritedStyle,
) -> Unit {
  if !is_paint_server_context(parent_tag) {
    skip_to_end_tag(parser, tag_name)
    return
  }
  let stops = parse_gradient_stops(parser, tag_name, attrs, ctx, gradient_style)
  parse_radial_gradient_with_stops(attrs, stops, ctx)
}

///|
fn parse_radial_gradient_with_stops(
  attrs : Array[(String, String)],
  stops : Array[GradientStop],
  ctx : SVGParseContext,
) -> Unit {
  let id = match get_attr(attrs, "id") {
    Some(v) => v
    None => ""
  }
  if id.length() == 0 {
    return
  }
  let reference = match get_href_attr(attrs) {
    Some(v) => parse_url_ref(v)
    None => None
  }
  let cx = parse_gradient_coord(get_attr(attrs, "cx"), 0.5)
  let cy = parse_gradient_coord(get_attr(attrs, "cy"), 0.5)
  let r = parse_gradient_coord(get_attr(attrs, "r"), 0.5)
  let fx = match get_attr(attrs, "fx") {
    Some(v) => parse_gradient_coord(Some(v), cx)
    None => cx
  }
  let fy = match get_attr(attrs, "fy") {
    Some(v) => parse_gradient_coord(Some(v), cy)
    None => cy
  }
  let spread_method = match get_attr(attrs, "spreadMethod") {
    Some(v) => parse_spread_method(v)
    None => Pad
  }
  let units = match get_attr(attrs, "gradientUnits") {
    Some(v) => parse_gradient_units(v)
    None => ObjectBoundingBox
  }
  let transform = match get_attr(attrs, "gradientTransform") {
    Some(v) => parse_transform(v)
    None => Transform::identity()
  }
  let grad = {
    ..RadialGradient::new(cx, cy, r, stops),
    fx,
    fy,
    spread_method,
    units,
    transform,
  }
  let mut specified = 0
  if get_attr(attrs, "cx") is Some(_) {
    specified = specified | 1
  }
  if get_attr(attrs, "cy") is Some(_) {
    specified = specified | 2
  }
  if get_attr(attrs, "r") is Some(_) {
    specified = specified | 4
  }
  if get_attr(attrs, "fx") is Some(_) {
    specified = specified | 8
  }
  if get_attr(attrs, "fy") is Some(_) {
    specified = specified | 16
  }
  if get_attr(attrs, "spreadMethod") is Some(_) {
    specified = specified | 32
  }
  if get_attr(attrs, "gradientUnits") is Some(_) {
    specified = specified | 64
  }
  if get_attr(attrs, "gradientTransform") is Some(_) {
    specified = specified | 128
  }
  if !stops.is_empty() {
    specified = specified | 256
  }
  ctx.gradients.add_definition(id, Radial(grad), reference, specified)
}

///|
fn parse_gradient_coord(value : String?, default : Double) -> Double {
  match value {
    Some(v) => {
      let (val, _) = parse_length_or_percent(v)
      val
    }
    None => default
  }
}

///|
fn parse_spread_method(value : String) -> SpreadMethod {
  match trim_string(value) {
    "repeat" => Repeat
    "reflect" => Reflect
    _ => Pad
  }
}

///|
fn parse_gradient_stops(
  parser : SVGParser,
  parent_tag : String,
  parent_attrs : Array[(String, String)],
  ctx : SVGParseContext,
  parent_style : InheritedStyle,
) -> Array[GradientStop] {
  let stops : Array[GradientStop] = []
  let depth = ctx.element_stack.length()
  let parent_element = if depth < ctx.selector_element_slots.length() {
    match ctx.selector_element_slots[depth] {
      Some(element) => element
      None => ctx.record_selector_element(parent_tag, parent_attrs)
    }
  } else {
    ctx.record_selector_element(parent_tag, parent_attrs)
  }
  ctx.element_stack.push(parent_element)
  ctx.style_stack.push(parent_style)
  ctx.sibling_index_stack.push(0)
  ctx.sibling_count_stack.push(count_direct_child_elements(parser, parent_tag))
  ctx.previous_sibling_stack.push(None)
  while !parser.is_end() {
    let (element, has_children) = match parser.advance_event() {
      XmlStart(element) => (Some(element), true)
      XmlEmpty(element) => (Some(element), false)
      XmlEnd(name) => {
        if name == parent_tag {
          break
        }
        (None, false)
      }
      XmlEof => break
      _ => (None, false)
    }
    match element {
      Some(element) => {
        let tag_name = element.name
        let attrs = element.attributes
        let current_index = ctx.sibling_index_stack.length() - 1
        ctx.sibling_index_stack[current_index] += 1
        let _ = ctx.record_selector_element(tag_name, attrs)
        if tag_name == "stop" {
          let temp = SVGNode::new(Group)
          let stop_cascaded = ctx.cascaded_style(tag_name, attrs)
          let stop_style = resolve_computed_style(
            temp,
            attrs,
            stop_cascaded,
            ctx.current_style(),
            false,
            ctx.current_length_viewport().unwrap_or((300.0, 150.0)),
            ctx.css_viewport,
            stylesheets=ctx.stylesheets,
            sample_time_seconds=ctx.sample_time_seconds,
          )
          stops.push(parse_gradient_stop(attrs, stop_style))
        }
        let child_depth = ctx.element_stack.length()
        if child_depth < ctx.selector_element_slots.length() {
          ctx.previous_sibling_stack[current_index] = ctx.selector_element_slots[child_depth]
        }
        if has_children {
          skip_to_end_tag(parser, tag_name)
        }
      }
      None => ()
    }
  }
  let _ = ctx.previous_sibling_stack.pop()
  let _ = ctx.sibling_count_stack.pop()
  let _ = ctx.sibling_index_stack.pop()
  let _ = ctx.style_stack.pop()
  let _ = ctx.element_stack.pop()
  stops
}

///|
fn parse_gradient_stop(
  attrs : Array[(String, String)],
  style : InheritedStyle,
) -> GradientStop {
  let offset = match get_attr(attrs, "offset") {
    Some(v) => parse_gradient_offset(v)
    None => 0.0
  }
  let color = style.stop_color
  let mut opacity = style.stop_opacity
  if opacity < 0.0 {
    opacity = 0.0
  }
  if opacity > 1.0 {
    opacity = 1.0
  }
  let final_color = if opacity < 1.0 {
    Color::rgba(
      color.r,
      color.g,
      color.b,
      (color.a.to_double() * opacity).round().to_int(),
    )
  } else {
    color
  }
  { offset, color: final_color }
}

///|
fn parse_gradient_offset(value : String) -> Double {
  let v = trim_string(value)
  let t = if string_ends_with(v, "%") {
    let cleaned = build_substring(v, 0, v.length() - 1)
    parse_number(cleaned) / 100.0
  } else {
    parse_number(v)
  }
  if t < 0.0 {
    0.0
  } else if t > 1.0 {
    1.0
  } else {
    t
  }
}