///|
/// Parse CSS transform property
/// Supports translate, scale, rotate, and skew in the 2D hit-test subset.
fn parse_transform(value : String) -> @style.Transform {
  let v = value.trim().to_lower()
  // Handle none
  if v == "none" || v.is_empty() {
    return @style.Transform::none()
  }
  let mut translate_x = @style.TranslateValue::Length(0.0)
  let mut translate_y = @style.TranslateValue::Length(0.0)
  let mut scale_x = 1.0
  let mut scale_y = 1.0
  let mut rotate_degrees = 0.0
  let mut skew_x_degrees = 0.0
  let mut skew_y_degrees = 0.0
  let mut has_matrix = false
  let mut matrix_a = 1.0
  let mut matrix_b = 0.0
  let mut matrix_c = 0.0
  let mut matrix_d = 1.0
  let mut matrix_e = 0.0
  let mut matrix_f = 0.0
  let mut matrix3d : Array[Double] = []
  // Parse transform functions
  // We handle each function in sequence
  let chars = v.to_array()
  let len = chars.length()
  let mut i = 0
  while i < len {
    // Skip whitespace
    while i < len && (chars[i] == ' ' || chars[i] == '\t') {
      i = i + 1
    }
    if i >= len {
      break
    }
    // Find function name
    let func_start = i
    while i < len && chars[i] != '(' {
      i = i + 1
    }
    if i >= len {
      break
    }
    let func_name = chars_to_string(chars[func_start:i])
    i = i + 1 // Skip '('
    // Find function arguments (handle nested parens)
    let args_start = i
    let mut paren_depth = 1
    while i < len && paren_depth > 0 {
      if chars[i] == '(' {
        paren_depth = paren_depth + 1
      } else if chars[i] == ')' {
        paren_depth = paren_depth - 1
      }
      i = i + 1
    }
    let args_str = chars_to_string(chars[args_start:i - 1])
    // Parse transform function
    match func_name.trim() {
      "translate" => {
        // translate(x) or translate(x, y)
        let args = split_by_comma(args_str)
        if args.length() >= 1 {
          translate_x = parse_translate_value(args[0])
        }
        if args.length() >= 2 {
          translate_y = parse_translate_value(args[1])
        }
      }
      "translatex" => translate_x = parse_translate_value(args_str)
      "translatey" => translate_y = parse_translate_value(args_str)
      "translate3d" => {
        // translate3d(x, y, z) - we only use x and y
        let args = split_by_comma(args_str)
        if args.length() >= 1 {
          translate_x = parse_translate_value(args[0])
        }
        if args.length() >= 2 {
          translate_y = parse_translate_value(args[1])
        }
      }
      "scale" => {
        // scale(x) or scale(x, y)
        let args = split_by_comma(args_str)
        if args.length() >= 1 {
          let sx = parse_scale_value(args[0])
          scale_x = scale_x * sx
          scale_y = scale_y * sx // If only one arg, use same scale for both
        }
        if args.length() >= 2 {
          scale_y = scale_y /
            scale_x *
            parse_scale_value(args[1]) *
            scale_x /
            scale_x
          // Actually: if second arg provided, override scale_y
          scale_y = parse_scale_value(args[1])
        }
      }
      "scalex" => scale_x = scale_x * parse_scale_value(args_str)
      "scaley" => scale_y = scale_y * parse_scale_value(args_str)
      "scale3d" => {
        // scale3d(x, y, z) - we only use x and y
        let args = split_by_comma(args_str)
        if args.length() >= 1 {
          scale_x = scale_x * parse_scale_value(args[0])
        }
        if args.length() >= 2 {
          scale_y = scale_y * parse_scale_value(args[1])
        }
      }
      "rotate" =>
        rotate_degrees = rotate_degrees + parse_rotate_degrees(args_str)
      "skewx" => skew_x_degrees += parse_rotate_degrees(args_str)
      "skewy" => skew_y_degrees += parse_rotate_degrees(args_str)
      "skew" => {
        let args = split_by_comma(args_str)
        if args.length() >= 1 {
          skew_x_degrees += parse_rotate_degrees(args[0])
        }
        if args.length() >= 2 {
          skew_y_degrees += parse_rotate_degrees(args[1])
        }
      }
      "matrix" => {
        let args = split_by_comma(args_str)
        if args.length() >= 6 {
          has_matrix = true
          matrix_a = parse_scale_value(args[0])
          matrix_b = parse_scale_value(args[1])
          matrix_c = parse_scale_value(args[2])
          matrix_d = parse_scale_value(args[3])
          matrix_e = parse_scale_value(args[4])
          matrix_f = parse_scale_value(args[5])
        }
      }
      "matrix3d" => {
        let args = split_by_comma(args_str)
        if args.length() >= 16 {
          has_matrix = true
          matrix_a = parse_scale_value(args[0])
          matrix_b = parse_scale_value(args[1])
          matrix_c = parse_scale_value(args[4])
          matrix_d = parse_scale_value(args[5])
          matrix_e = parse_scale_value(args[12])
          matrix_f = parse_scale_value(args[13])
          matrix3d = []
          for k = 0; k < 16; k = k + 1 {
            matrix3d.push(parse_scale_value(args[k]))
          }
        }
      }
      _ => () // Ignore unsupported functions like perspective.
    }
  }
  {
    translate_x,
    translate_y,
    scale_x,
    scale_y,
    rotate_degrees,
    skew_x_degrees,
    skew_y_degrees,
    has_matrix,
    matrix_a,
    matrix_b,
    matrix_c,
    matrix_d,
    matrix_e,
    matrix_f,
    matrix3d,
  }
}

///|
fn parse_rotate_degrees(value : String) -> Double {
  let v = value.trim()
  if v.is_empty() {
    return 0.0
  }
  if v.has_suffix("deg") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 3))
    return @string.parse_double(num_str.trim()) catch { _ => 0.0 }
  }
  if v.has_suffix("turn") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 4))
    let turns = @string.parse_double(num_str.trim()) catch { _ => return 0.0 }
    return turns * 360.0
  }
  // `grad` must be checked before `rad`: it also ends with "rad".
  // 400grad = 360deg, so 1grad = 0.9deg.
  if v.has_suffix("grad") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 4))
    let gradians = @string.parse_double(num_str.trim()) catch {
      _ => return 0.0
    }
    return gradians * 0.9
  }
  if v.has_suffix("rad") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 3))
    let radians = @string.parse_double(num_str.trim()) catch { _ => return 0.0 }
    return radians * 57.29577951308232
  }
  @string.parse_double(v) catch {
    _ => 0.0
  }
}

///|
/// Parse a scale value (number, no unit)
fn parse_scale_value(value : String) -> Double {
  let v = value.trim()
  if v.is_empty() {
    return 1.0
  }
  @string.parse_double(v) catch {
    _ => 1.0
  }
}

///|
/// Parse CSS zoom property
/// Supports: normal, number, percentage
fn parse_zoom(value : String) -> Double {
  let v = value.trim().to_lower()
  if v.is_empty() || v == "normal" {
    return 1.0
  }
  // Check for percentage (e.g., "150%")
  if v.has_suffix("%") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 1))
    let n = @string.parse_double(num_str.trim()) catch { _ => return 1.0 }
    return n / 100.0
  }
  // Parse as number (e.g., "1.5" or "2")
  @string.parse_double(v) catch {
    _ => 1.0
  }
}

///|
/// Parse a single translate value (can be length or percentage)
fn parse_translate_value(value : String) -> @style.TranslateValue {
  let v = value.trim()
  if v.is_empty() {
    return Length(0.0)
  }
  // Check for percentage
  if v.has_suffix("%") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 1))
    let n = @string.parse_double(num_str.trim()) catch {
      _ => return Length(0.0)
    }
    Percent(n / 100.0)
  } else if v.has_suffix("px") {
    let num_str = view_to_string(v.view(end_offset=v.length() - 2))
    let n = @string.parse_double(num_str.trim()) catch {
      _ => return Length(0.0)
    }
    Length(n)
  } else if v.has_suffix("em") || v.has_suffix("rem") {
    // em/rem - convert to pixels assuming 16px base
    let suffix_len = if v.has_suffix("rem") { 3 } else { 2 }
    let num_str = view_to_string(v.view(end_offset=v.length() - suffix_len))
    let n = @string.parse_double(num_str.trim()) catch {
      _ => return Length(0.0)
    }
    Length(n * 16.0)
  } else {
    // Try parsing as plain number (assumed px)
    let n = @string.parse_double(v) catch { _ => return Length(0.0) }
    Length(n)
  }
}

///|
/// Split string by comma (for transform arguments)
fn split_by_comma(s : String) -> Array[String] {
  let result : Array[String] = []
  let current = StringBuilder::new()
  for c in s {
    if c == ',' {
      result.push(current.to_string())
      current.reset()
    } else {
      current.write_char(c)
    }
  }
  let last = current.to_string()
  if !last.is_empty() {
    result.push(last)
  }
  result
}

///|
/// Convert char array slice to string
fn chars_to_string(chars : ArrayView[Char]) -> String {
  let sb = StringBuilder::new()
  for c in chars {
    sb.write_char(c)
  }
  sb.to_string()
}

///|
/// Check if a string is a flex-basis value (has units, % or is auto/content)
fn is_flex_basis_value(s : String) -> Bool {
  let v = s.trim().to_lower()
  if v == "auto" || v == "content" {
    return true
  }
  // Check for common dimension suffixes
  v.has_suffix("%") ||
  v.has_suffix("px") ||
  v.has_suffix("em") ||
  v.has_suffix("rem") ||
  v.has_suffix("dvw") ||
  v.has_suffix("svw") ||
  v.has_suffix("lvw") ||
  v.has_suffix("vw") ||
  v.has_suffix("dvh") ||
  v.has_suffix("svh") ||
  v.has_suffix("lvh") ||
  v.has_suffix("vh")
}

///|
fn strip_inline_important_marker(
  value : String,
) -> (String, @cascade.Importance) {
  let trimmed = value.trim().to_owned()
  let compact = strip_css_ascii_whitespace(trimmed.to_lower())
  if !compact.has_suffix("!important") {
    return (trimmed, Normal)
  }
  let important_len = 9
  if trimmed.length() <= important_len {
    return (trimmed, Normal)
  }
  let mut bang_pos = trimmed.length() - important_len
  while bang_pos > 0 {
    let c = trimmed[bang_pos - 1].to_int().unsafe_to_char()
    if is_css_ascii_whitespace(c) {
      bang_pos -= 1
    } else {
      break
    }
  }
  if bang_pos <= 0 {
    return (trimmed, Normal)
  }
  if trimmed[bang_pos - 1].to_int().unsafe_to_char() != '!' {
    return (trimmed, Normal)
  }
  let stripped = view_to_string(trimmed.view(end_offset=bang_pos - 1))
    .trim()
    .to_owned()
  if stripped.is_empty() {
    return (trimmed, Normal)
  }
  (stripped, Important)
}