///|
/// CSS Property Metadata
/// Defines initial values and inheritance behavior for layout properties

///|
/// Whether a property is inherited by default
pub fn is_inherited(property : String) -> Bool {
  match property {
    // Text-related properties (inherited)
    "direction"
    | "writing-mode"
    | "text-align"
    | "line-height"
    | "white-space"
    | "font-size"
    | "font-family"
    | "font-weight"
    | "font-style"
    | "color"
    | "visibility" => true
    // Table properties that are inherited
    "caption-side" | "border-collapse" | "border-spacing" => true
    // Containment is not inherited
    "contain" => false
    "pointer-events" => true
    // All layout properties are not inherited
    _ => false
  }
}

///|
/// Get the initial value for a property as a string
pub fn initial_value(property : String) -> String {
  match property {
    // Display
    "display" => "block"
    "position" => "relative"
    "float" => "none"
    "clear" => "none"
    // Overflow
    "overflow" | "overflow-x" | "overflow-y" => "visible"
    "pointer-events" => "auto"
    "scroll-snap-type" => "none"
    "scroll-snap-align" => "none"
    // Sizing
    "width" | "height" | "min-width" | "min-height" => "auto"
    "max-width" | "max-height" => "none"
    // Box model
    "margin"
    | "margin-top"
    | "margin-right"
    | "margin-bottom"
    | "margin-left" => "0"
    "margin-trim" => "none"
    "padding"
    | "padding-top"
    | "padding-right"
    | "padding-bottom"
    | "padding-left" => "0"
    "border-width"
    | "border-top-width"
    | "border-right-width"
    | "border-bottom-width"
    | "border-left-width" => "0"
    // Flexbox container
    "flex-direction" => "row"
    "flex-wrap" => "nowrap"
    "justify-content" => "flex-start"
    "align-items" => "stretch"
    "align-content" => "stretch"
    // Flexbox item
    "align-self" => "auto"
    "flex-grow" => "0"
    "flex-shrink" => "1"
    "flex-basis" => "auto"
    // Gap (grid-gap is legacy alias)
    "gap"
    | "row-gap"
    | "column-gap"
    | "grid-gap"
    | "grid-row-gap"
    | "grid-column-gap" => "0"
    "columns" | "column-count" | "column-width" => "auto"
    "column-fill" => "balance"
    "break-before" | "break-after" | "break-inside" | "page-break-inside" =>
      "auto"
    "column-span" => "none"
    // Aspect ratio
    "aspect-ratio" => "auto"
    // Inset
    "top" | "right" | "bottom" | "left" | "inset" => "auto"
    // Grid container
    "grid-template-rows" | "grid-template-columns" => "none"
    "grid-auto-rows" | "grid-auto-columns" => "auto"
    "grid-auto-flow" => "row"
    // Grid item
    "grid-row-start"
    | "grid-row-end"
    | "grid-column-start"
    | "grid-column-end" => "auto"
    // Text (inherited)
    "direction" => "ltr"
    "writing-mode" => "horizontal-tb"
    "text-align" => "start"
    "font-weight" => "400"
    "white-space" => "normal"
    "text-overflow" => "clip"
    "text-decoration" | "text-decoration-line" => "none"
    "box-shadow" => "none"
    // Containment
    "contain" => "none"
    "contain-intrinsic-size"
    | "contain-intrinsic-inline-size"
    | "contain-intrinsic-block-size" => "none"
    // Table properties
    "caption-side" => "top"
    "border-collapse" => "separate"
    "border-spacing" => "0"
    "table-layout" => "auto"
    // Default
    _ => "auto"
  }
}

///|
/// Parse a dimension value from string
pub fn parse_dimension(value : String) -> @types.Dimension {
  let v = value.trim()
  if v == "auto" || v == "none" {
    return @types.Dimension::Auto
  }
  // Intrinsic sizing keywords
  if v == "min-content" {
    return @types.Dimension::MinContent
  }
  if v == "max-content" {
    return @types.Dimension::MaxContent
  }
  // fit-content()
  if v.has_prefix("fit-content(") && v.has_suffix(")") {
    // Extract content between "fit-content(" and ")"
    let v_str = v.to_string()
    let inner = v_str.unsafe_substring(start=12, end=v_str.length() - 1)
    // Parse the inner value as a length
    match inner.strip_suffix("px") {
      Some(num_str) => {
        let n = @string.parse_double(num_str.to_string()) catch {
          _ => return @types.Dimension::Auto
        }
        return @types.Dimension::FitContent(n)
      }
      None => ()
    }
    // Try percentage
    match inner.strip_suffix("%") {
      Some(num_str) => {
        let n = @string.parse_double(num_str.to_string()) catch {
          _ => return @types.Dimension::Auto
        }
        // For fit-content, percentage is relative to available space
        // Store as negative to differentiate from length
        return @types.Dimension::FitContent(n)
      }
      None => ()
    }
    return @types.Dimension::Auto
  }
  // Try to parse as length (px)
  match v.strip_suffix("px") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n)
    }
    None => ()
  }
  // Try to parse as percentage
  match v.strip_suffix("%") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Percent(n / 100.0)
    }
    None => ()
  }
  // Try to parse as plain number (treated as px)
  let n = @string.parse_double(v.to_string()) catch {
    _ => return @types.Dimension::Auto
  }
  @types.Dimension::Length(n)
}

///|
/// Extract border width from shorthand value
/// Handles formats like "3px", "solid 3px", "1px solid red", "none", etc.
pub fn extract_border_width(value : String) -> @types.Dimension {
  let v = value.trim()

  // Handle "none" case
  if v == "none" || v == "0" {
    return @types.Dimension::Length(0.0)
  }

  // Split by whitespace and look for a dimension value
  let parts = split_whitespace(v.to_string())
  let mut has_visible_style = false
  for part in parts {
    let lower = part.to_lower()
    match lower {
      "thin" => return @types.Dimension::Length(1.0)
      "medium" => return @types.Dimension::Length(3.0)
      "thick" => return @types.Dimension::Length(5.0)
      "solid"
      | "dotted"
      | "dashed"
      | "double"
      | "groove"
      | "ridge"
      | "inset"
      | "outset" => {
        has_visible_style = true
        continue
      }
      "none" | "hidden" => continue
      _ => ()
    }
    // Check if this part looks like a dimension (ends with px or is a number)
    if part.has_suffix("px") {
      match part.strip_suffix("px") {
        Some(num_str) => {
          let n = @string.parse_double(num_str.to_string()) catch {
            _ => continue
          }
          return @types.Dimension::Length(n)
        }
        None => continue
      }
    }
    // Try plain number
    let first_char = part[0].to_int()
    if first_char >= '0'.to_int() && first_char <= '9'.to_int() {
      let n = @string.parse_double(part) catch { _ => continue }
      return @types.Dimension::Length(n)
    }
  }

  // Compatible default for style-only shorthand like "border: solid".
  if has_visible_style {
    @types.Dimension::Length(1.5)
  } else {
    // Default: no border
    @types.Dimension::Length(0.0)
  }
}

///|
/// Split string by whitespace into parts
fn split_whitespace(s : String) -> Array[String] {
  let result : Array[String] = []
  let current = StringBuilder::new()
  for c in s {
    if c == ' ' || c == '\t' || c == '\n' {
      if current.to_string().length() > 0 {
        result.push(current.to_string())
        current.reset()
      }
    } else {
      current.write_char(c)
    }
  }
  if current.to_string().length() > 0 {
    result.push(current.to_string())
  }
  result
}

///|
/// Parse display value
pub fn parse_display(value : String) -> @types.Display {
  let normalized = value.trim().to_lower().to_string()
  let parts = split_whitespace(normalized)
  if parts.length() == 0 {
    return @types.Display::Block
  }
  if parts.length() == 2 {
    let first = parts[0]
    let second = parts[1]
    if (first == "math" && second == "inline") ||
      (first == "inline" && second == "math") {
      return @types.Display::Inline
    }
    if (first == "math" && second == "block") ||
      (first == "block" && second == "math") {
      return @types.Display::Block
    }
    if (first == "flow-root" && second == "list-item") ||
      (first == "list-item" && second == "flow-root") {
      return @types.Display::FlowRoot
    }
  }
  if parts.length() > 1 {
    return @types.Display::Block
  }
  match parts[0] {
    "block" => @types.Display::Block
    "inline" => @types.Display::Inline
    "inline-block" => @types.Display::InlineBlock
    "flex" => @types.Display::Flex
    "inline-flex" => @types.Display::InlineFlex
    "grid" => @types.Display::Grid
    "inline-grid" => @types.Display::InlineGrid
    "none" => @types.Display::None
    "contents" => @types.Display::Contents
    "flow-root" => @types.Display::FlowRoot
    "math" => @types.Display::Inline
    // Table display types
    "table" => @types.Display::Table
    "inline-table" => @types.Display::InlineTable
    "table-row" => @types.Display::TableRow
    "table-cell" => @types.Display::TableCell
    "table-caption" => @types.Display::TableCaption
    "table-row-group" => @types.Display::TableRowGroup
    "table-header-group" => @types.Display::TableHeaderGroup
    "table-footer-group" => @types.Display::TableFooterGroup
    "table-column" => @types.Display::TableColumn
    "table-column-group" => @types.Display::TableColumnGroup
    // Ruby internal display types are inline-level for layout-tree comparison.
    // (Full ruby formatting context is not yet implemented.)
    "ruby" => @types.Display::Inline
    "ruby-base" => @types.Display::Inline
    "ruby-base-container" => @types.Display::Inline
    "ruby-text" => @types.Display::Inline
    "ruby-text-container" => @types.Display::Inline
    _ => @types.Display::Block
  }
}

///|
/// Parse position value
pub fn parse_position(value : String) -> @types.Position {
  match value.trim().to_lower() {
    "static" => @types.Position::Static
    "absolute" => @types.Position::Absolute
    "fixed" => @types.Position::Fixed
    "relative" | "sticky" => @types.Position::Relative
    _ => @types.Position::Static // Default is static
  }
}

///|
/// Parse float value
pub fn parse_float(value : String) -> @types.Float {
  match value.trim().to_lower() {
    "left" => @types.Float::Left
    "right" => @types.Float::Right
    "none" => @types.Float::None
    _ => @types.Float::None
  }
}

///|
pub fn parse_float_with_direction(
  value : String,
  direction : @style.Direction,
) -> @types.Float {
  match value.trim().to_lower() {
    "inline-start" =>
      if direction.is_rtl() {
        @types.Float::Right
      } else {
        @types.Float::Left
      }
    "inline-end" =>
      if direction.is_rtl() {
        @types.Float::Left
      } else {
        @types.Float::Right
      }
    _ => parse_float(value)
  }
}

///|
/// Parse clear value
pub fn parse_clear(value : String) -> @types.Clear {
  match value.trim().to_lower() {
    "left" => @types.Clear::Left
    "right" => @types.Clear::Right
    "both" => @types.Clear::Both
    "none" => @types.Clear::None
    _ => @types.Clear::None
  }
}

///|
pub fn parse_clear_with_direction(
  value : String,
  direction : @style.Direction,
) -> @types.Clear {
  match value.trim().to_lower() {
    "inline-start" =>
      if direction.is_rtl() {
        @types.Clear::Right
      } else {
        @types.Clear::Left
      }
    "inline-end" =>
      if direction.is_rtl() {
        @types.Clear::Left
      } else {
        @types.Clear::Right
      }
    _ => parse_clear(value)
  }
}

///|
/// Parse flex-direction value
pub fn parse_flex_direction(value : String) -> @types.FlexDirection {
  match value.trim().to_lower() {
    "row" => @types.FlexDirection::Row
    "row-reverse" => @types.FlexDirection::RowReverse
    "column" => @types.FlexDirection::Column
    "column-reverse" => @types.FlexDirection::ColumnReverse
    _ => @types.FlexDirection::Row
  }
}

///|
/// Parse flex-wrap value
pub fn parse_flex_wrap(value : String) -> @types.FlexWrap {
  match value.trim().to_lower() {
    "nowrap" => @types.FlexWrap::NoWrap
    "wrap" => @types.FlexWrap::Wrap
    "wrap-reverse" => @types.FlexWrap::WrapReverse
    _ => @types.FlexWrap::NoWrap
  }
}

///|
fn tokenize_alignment_value(value : String) -> Array[String] {
  value
  .trim()
  .to_lower()
  .split(" ")
  .map(fn(part) { part.to_string().trim().to_string() })
  .filter(fn(part) { !part.is_empty() })
  .collect()
}

///|
fn parse_alignment_keyword(value : String) -> @types.Alignment {
  match value {
    "flex-start" => @types.Alignment::FlexStart
    "flex-end" => @types.Alignment::FlexEnd
    "start" => @types.Alignment::Start
    "end" => @types.Alignment::End
    "left" => @types.Alignment::Left
    "right" => @types.Alignment::Right
    "center" => @types.Alignment::Center
    "space-between" => @types.Alignment::SpaceBetween
    "space-around" => @types.Alignment::SpaceAround
    "space-evenly" => @types.Alignment::SpaceEvenly
    "stretch" => @types.Alignment::Stretch
    "baseline" | "first baseline" => @types.Alignment::Baseline
    // Fallback alignment for last-baseline in axis alignment.
    "last baseline" => @types.Alignment::End
    _ => @types.Alignment::FlexStart
  }
}

///|
fn parse_alignment_segment(
  tokens : Array[String],
  start : Int,
) -> (String, Int)? {
  if start >= tokens.length() {
    return None
  }
  let head = tokens[start]
  if start + 1 < tokens.length() {
    let next = tokens[start + 1]
    if head == "safe" ||
      head == "unsafe" ||
      ((head == "first" || head == "last") && next == "baseline") {
      return Some((head + " " + next, start + 2))
    }
  }
  Some((head, start + 1))
}

///|
/// Parse alignment value (justify-content, align-items, align-content)
/// Note: flex-start/flex-end are flex-relative (affected by wrap-reverse)
///       start/end are physical/logical (not affected by wrap-reverse)
pub fn parse_alignment_with_overflow(
  value : String,
) -> (@types.Alignment, Bool) {
  let tokens = tokenize_alignment_value(value)
  if tokens.is_empty() {
    return (@types.Alignment::FlexStart, false)
  }
  let mut offset = 0
  let mut is_safe = false
  let mut is_unsafe = false
  if tokens[0] == "safe" {
    is_safe = true
    offset = 1
  } else if tokens[0] == "unsafe" {
    is_unsafe = true
    offset = 1
  }
  if offset >= tokens.length() {
    return (@types.Alignment::FlexStart, false)
  }
  let keyword = if offset + 1 < tokens.length() &&
    tokens[offset + 1] == "baseline" &&
    (tokens[offset] == "first" || tokens[offset] == "last") {
    tokens[offset] + " baseline"
  } else {
    tokens[offset]
  }
  let parsed = parse_alignment_keyword(keyword)
  let parsed = if is_safe {
    match parsed {
      @types.Alignment::Center
      | @types.Alignment::End
      | @types.Alignment::FlexEnd => @types.Alignment::Start
      _ => parsed
    }
  } else {
    parsed
  }
  (parsed, is_unsafe)
}

///|
pub fn parse_alignment(value : String) -> @types.Alignment {
  let (alignment, _) = parse_alignment_with_overflow(value)
  alignment
}

///|
/// Parse place-content shorthand.
/// Returns (align_content, justify_content).
pub fn parse_place_content_with_overflow(
  value : String,
) -> ((@types.Alignment, Bool), (@types.Alignment, Bool))? {
  let tokens = tokenize_alignment_value(value)
  if tokens.is_empty() {
    return None
  }
  match parse_alignment_segment(tokens, 0) {
    Some((first, next_index)) => {
      let first_alignment = parse_alignment_with_overflow(first)
      if next_index >= tokens.length() {
        return Some((first_alignment, first_alignment))
      }
      match parse_alignment_segment(tokens, next_index) {
        Some((second, _)) =>
          Some((first_alignment, parse_alignment_with_overflow(second)))
        None => Some((first_alignment, first_alignment))
      }
    }
    None => None
  }
}

///|
/// Parse place-content shorthand.
/// Returns (align_content, justify_content).
pub fn parse_place_content(
  value : String,
) -> (@types.Alignment, @types.Alignment)? {
  match parse_place_content_with_overflow(value) {
    Some(((align_content, _), (justify_content, _))) =>
      Some((align_content, justify_content))
    None => None
  }
}

///|
/// Parse align-self value
pub fn parse_align_self(value : String) -> @types.AlignSelf {
  match value.trim().to_lower() {
    "auto" => @types.AlignSelf::Auto
    "flex-start" | "start" => @types.AlignSelf::Start
    "flex-end" | "end" => @types.AlignSelf::End
    "center" => @types.AlignSelf::Center
    "stretch" => @types.AlignSelf::Stretch
    "baseline" | "first baseline" => @types.AlignSelf::Baseline
    // Fallback alignment for last-baseline in self alignment.
    "last baseline" => @types.AlignSelf::End
    _ => @types.AlignSelf::Auto
  }
}

///|
/// Parse overflow value
pub fn parse_overflow(value : String) -> @types.Overflow {
  match value.trim().to_lower() {
    "visible" => @types.Overflow::Visible
    "hidden" => @types.Overflow::Hidden
    "clip" => @types.Overflow::Clip
    "scroll" => @types.Overflow::Scroll
    "auto" => @types.Overflow::Auto
    _ => @types.Overflow::Visible
  }
}

///|
/// Parse scroll-snap-type value
/// Supports a minimal subset: none | x mandatory | y mandatory | both mandatory.
pub fn parse_scroll_snap_type(value : String) -> @style.ScrollSnapType {
  let tokens = value
    .trim()
    .to_lower()
    .split(" ")
    .map(fn(part) { part.to_string().trim().to_string() })
    .filter(fn(part) { !part.is_empty() })
    .collect()
  if tokens.is_empty() {
    return @style.ScrollSnapType::none()
  }

  let mut axis = @style.ScrollSnapAxis::None
  let mut strictness = @style.ScrollSnapStrictness::None
  for token in tokens {
    match token {
      "none" => return @style.ScrollSnapType::none()
      "x" | "inline" => axis = @style.ScrollSnapAxis::X
      "y" | "block" => axis = @style.ScrollSnapAxis::Y
      "both" => axis = @style.ScrollSnapAxis::Both
      "mandatory" => strictness = @style.ScrollSnapStrictness::Mandatory
      "proximity" => strictness = @style.ScrollSnapStrictness::Proximity
      _ => ()
    }
  }
  if axis == @style.ScrollSnapAxis::None ||
    strictness == @style.ScrollSnapStrictness::None {
    @style.ScrollSnapType::none()
  } else {
    { axis, strictness }
  }
}

///|
/// Parse scroll-snap-align value as (inline, block).
/// Supports keywords: none | start | end | center.
pub fn parse_scroll_snap_align(
  value : String,
) -> (@style.ScrollSnapAlign, @style.ScrollSnapAlign) {
  let tokens = value
    .trim()
    .to_lower()
    .split(" ")
    .map(fn(part) { part.to_string().trim().to_string() })
    .filter(fn(part) { !part.is_empty() })
    .collect()
  if tokens.is_empty() {
    return (@style.ScrollSnapAlign::None, @style.ScrollSnapAlign::None)
  }
  if tokens.length() == 1 {
    let keyword = parse_scroll_snap_align_keyword(tokens[0])
    return (keyword, keyword)
  }
  let block = parse_scroll_snap_align_keyword(tokens[0])
  let inline = parse_scroll_snap_align_keyword(tokens[1])
  (inline, block)
}

///|
fn parse_scroll_snap_align_keyword(value : String) -> @style.ScrollSnapAlign {
  match value.trim().to_lower() {
    "none" => @style.ScrollSnapAlign::None
    "start" => @style.ScrollSnapAlign::Start
    "end" => @style.ScrollSnapAlign::End
    "center" => @style.ScrollSnapAlign::Center
    _ => @style.ScrollSnapAlign::None
  }
}

///|
/// Parse white-space value
pub fn parse_white_space(value : String) -> @style.WhiteSpace {
  match value.trim().to_lower() {
    "normal" => @style.WhiteSpace::Normal
    "nowrap" => @style.WhiteSpace::Nowrap
    "pre" => @style.WhiteSpace::Pre
    "pre-wrap" => @style.WhiteSpace::PreWrap
    "pre-line" => @style.WhiteSpace::PreLine
    _ => @style.WhiteSpace::Normal
  }
}

///|
/// Parse writing-mode value
pub fn parse_writing_mode(value : String) -> @style.WritingMode {
  match value.trim().to_lower() {
    "horizontal-tb" => @style.WritingMode::HorizontalTb
    "vertical-rl" => @style.WritingMode::VerticalRl
    "vertical-lr" => @style.WritingMode::VerticalLr
    // Legacy values
    "lr" | "lr-tb" | "rl" | "rl-tb" => @style.WritingMode::HorizontalTb
    "tb" | "tb-rl" => @style.WritingMode::VerticalRl
    "tb-lr" => @style.WritingMode::VerticalLr
    _ => @style.WritingMode::HorizontalTb
  }
}

///|
/// Parse direction value (ltr/rtl)
pub fn parse_direction(value : String) -> @style.Direction {
  match value.trim().to_lower() {
    "rtl" => @style.Direction::Rtl
    "ltr" | _ => @style.Direction::Ltr
  }
}

///|
/// Parse text-align value
pub fn parse_text_align(value : String) -> @style.TextAlign {
  match value.trim().to_lower() {
    "center" => @style.TextAlign::Center
    "justify" => @style.TextAlign::Justify
    "left" => @style.TextAlign::Left
    "right" => @style.TextAlign::Right
    "end" => @style.TextAlign::End
    "start" | _ => @style.TextAlign::Start
  }
}

///|
/// Parse grid-auto-flow value
pub fn parse_grid_auto_flow(value : String) -> @types.GridAutoFlow {
  match value.trim().to_lower() {
    "row" => @types.GridAutoFlow::Row
    "column" => @types.GridAutoFlow::Column
    "row dense" | "dense row" => @types.GridAutoFlow::RowDense
    "column dense" | "dense column" => @types.GridAutoFlow::ColumnDense
    _ => @types.GridAutoFlow::Row
  }
}

///|
/// Parse a grid placement value (for grid-column-start, grid-row-end, etc.)
/// Supports: auto, , span 
pub fn parse_grid_placement(value : String) -> @types.GridPlacement {
  let v = value.trim().to_lower()
  if v == "auto" {
    return @types.GridPlacement::Auto
  }
  // Check for "span N" pattern
  if v.has_prefix("span") {
    let num_str = view_to_string(v.view(start_offset=4))
    let num = @string.parse_int(num_str.trim().to_string()) catch { _ => 1 }
    return @types.GridPlacement::Span(num)
  }
  // Otherwise, parse as integer (line number)
  let line = @string.parse_int(v.to_string()) catch { _ => 0 }
  if line != 0 {
    @types.GridPlacement::Line(line)
  } else if is_basic_custom_ident(v) {
    // Browser-compatible fallback: unresolved custom identifiers in placement
    // create an implicit track before the item (equivalent to line 2 start).
    @types.GridPlacement::Line(2)
  } else {
    @types.GridPlacement::Auto
  }
}

///|
/// Parse grid-column or grid-row shorthand
/// Formats: ,  / ,  / span 
pub fn parse_grid_line_shorthand(
  value : String,
) -> (@types.GridPlacement, @types.GridPlacement) {
  let v = value.trim()
  // Check for "/" separator
  let parts = v.split("/").map(fn(s) { s.trim().to_string() }).collect()
  if parts.length() == 2 {
    let start = parse_grid_placement(parts[0])
    let end = parse_grid_placement(parts[1])
    (start, end)
  } else {
    // Single value: applies to start, end is auto
    let start = parse_grid_placement(v.to_string())
    (start, @types.GridPlacement::Auto)
  }
}

///|
/// Parse grid-area shorthand:
///  /  /  / 
pub fn parse_grid_area_shorthand(
  value : String,
) -> (
  @types.GridPlacement,
  @types.GridPlacement,
  @types.GridPlacement,
  @types.GridPlacement,
) {
  let v = value.trim()
  let parts = v
    .split("/")
    .map(fn(s) { s.trim().to_string() })
    .filter(fn(s) { !s.is_empty() })
    .collect()
  if parts.length() >= 4 {
    (
      parse_grid_placement(parts[0]),
      parse_grid_placement(parts[1]),
      parse_grid_placement(parts[2]),
      parse_grid_placement(parts[3]),
    )
  } else if parts.length() == 3 {
    (
      parse_grid_placement(parts[0]),
      parse_grid_placement(parts[1]),
      parse_grid_placement(parts[2]),
      @types.GridPlacement::Auto,
    )
  } else if parts.length() == 2 {
    (
      parse_grid_placement(parts[0]),
      parse_grid_placement(parts[1]),
      @types.GridPlacement::Auto,
      @types.GridPlacement::Auto,
    )
  } else if parts.length() == 1 {
    let start = parse_grid_placement(parts[0])
    (start, start, @types.GridPlacement::Auto, @types.GridPlacement::Auto)
  } else {
    (
      @types.GridPlacement::Auto,
      @types.GridPlacement::Auto,
      @types.GridPlacement::Auto,
      @types.GridPlacement::Auto,
    )
  }
}

///|
fn is_basic_custom_ident(value : StringView) -> Bool {
  if value.length() == 0 {
    return false
  }
  let first = value[0]
  let first_ok = (first >= 'a' && first <= 'z') ||
    (first >= 'A' && first <= 'Z') ||
    first == '_' ||
    first == '-'
  if !first_ok {
    return false
  }
  for i = 1; i < value.length(); i = i + 1 {
    let c = value[i]
    let ok = (c >= 'a' && c <= 'z') ||
      (c >= 'A' && c <= 'Z') ||
      (c >= '0' && c <= '9') ||
      c == '_' ||
      c == '-'
    if !ok {
      return false
    }
  }
  true
}

///|
/// Parse a number value
pub fn parse_number(value : String) -> Double {
  @string.parse_double(value.trim().to_string()) catch {
    _ => 0.0
  }
}

///|
/// Parse an integer value
pub fn parse_integer(value : String) -> Int {
  @string.parse_int(value.trim().to_string()) catch {
    _ => 0
  }
}

///|
/// Parse grid-template-columns or grid-template-rows value
/// Delegate to the full parser to support minmax() and repeat().
pub fn parse_grid_template_tracks(
  value : String,
) -> Array[@types.TrackSizingFunction] {
  @parser.parse_grid_template_tracks_from_string(value)
}

///|
/// Parse grid-template shorthand: ` / `
pub fn parse_grid_template_shorthand(
  value : String,
) -> (Array[@types.TrackSizingFunction], Array[@types.TrackSizingFunction])? {
  let v = value.trim()
  if !v.contains("/") {
    return None
  }
  let parts = v.split("/").map(fn(s) { s.trim().to_string() }).collect()
  if parts.length() != 2 {
    return None
  }
  let rows = parse_grid_template_tracks(parts[0])
  let cols = parse_grid_template_tracks(parts[1])
  Some((rows, cols))
}

///|
/// Parse grid-template-areas CSS property value.
/// The value is a series of quoted strings like: "header header" "sidebar main" "footer footer"
pub fn parse_grid_template_areas_value(value : String) -> Array[String] {
  let result : Array[String] = []
  let v = value.trim()
  // Parse quoted strings - each quoted string is one row
  let mut i = 0
  while i < v.length() {
    let c = v[i].unsafe_to_char()
    if c == '"' || c == '\'' {
      let quote = c
      let mut end = i + 1
      while end < v.length() && v[end].unsafe_to_char() != quote {
        end = end + 1
      }
      if end > i + 1 {
        let row_str = view_to_string(v.view(start_offset=i + 1, end_offset=end))
        result.push(row_str.to_string())
      }
      i = end + 1
    } else {
      i = i + 1
    }
  }
  result
}

///|
/// Check if a value is a named grid area identifier (not a number, auto, or span keyword)
pub fn is_grid_area_name(value : String) -> Bool {
  let v = value.trim().to_lower()
  if v == "auto" ||
    v == "none" ||
    v == "inherit" ||
    v == "initial" ||
    v == "unset" {
    return false
  }
  if v.has_prefix("span") {
    return false
  }
  // Check if it's a number
  let parsed = @string.parse_int(v.to_string()) catch { _ => 0 }
  if parsed != 0 {
    return false
  }
  // Must be a valid custom ident
  is_basic_custom_ident(v)
}

///|
/// Parse aspect-ratio value
pub fn parse_aspect_ratio(value : String) -> Double? {
  let v = value.trim()
  if v == "auto" {
    return None
  }
  // Check for ratio format: "16 / 9" or "16/9"
  if v.contains("/") {
    // Find the slash position
    let mut slash_pos = -1
    for i = 0; i < v.length(); i = i + 1 {
      if v[i].to_int().unsafe_to_char() == '/' {
        slash_pos = i
        break
      }
    }
    if slash_pos > 0 {
      // Use view slicing
      let width_str = view_to_string(v.view(end_offset=slash_pos)).trim()
      let height_str = view_to_string(v.view(start_offset=slash_pos + 1)).trim()
      let w = @string.parse_double(width_str.to_string()) catch {
        _ => return None
      }
      let h = @string.parse_double(height_str.to_string()) catch {
        _ => return None
      }
      if h != 0.0 {
        return Some(w / h)
      }
      return None
    }
  }
  // Try parsing as a single number
  let n = @string.parse_double(v.to_string()) catch { _ => return None }
  Some(n)
}

///|
/// Parse CSS clip property value
/// Supports: auto, rect(top, right, bottom, left)
/// Note: This is a deprecated property but widely used for accessibility
pub fn parse_clip(value : String) -> @types.ClipRect {
  let v = value.trim().to_lower()
  if v == "auto" || v == "initial" || v == "unset" {
    return @types.ClipRect::Auto
  }
  // Parse rect(top, right, bottom, left)
  if v.has_prefix("rect(") && v.has_suffix(")") {
    // Extract the content inside rect()
    let content = view_to_string(
      v.view(start_offset=5, end_offset=v.length() - 1),
    ).trim()
    // Split by comma or space (both are valid in CSS)
    let parts : Array[String] = []
    let mut current = StringBuilder::new()
    for c in content.iter() {
      if c == ',' || c == ' ' {
        let s = current.to_string().trim().to_string()
        if !s.is_empty() {
          parts.push(s)
        }
        current = StringBuilder::new()
      } else {
        current.write_char(c)
      }
    }
    let last = current.to_string().trim().to_string()
    if !last.is_empty() {
      parts.push(last)
    }
    if parts.length() == 4 {
      let top = parse_clip_value(parts[0])
      let right = parse_clip_value(parts[1])
      let bottom = parse_clip_value(parts[2])
      let left = parse_clip_value(parts[3])
      return @types.ClipRect::Rect(top~, right~, bottom~, left~)
    }
  }
  @types.ClipRect::Auto
}

///|
/// Parse a single clip rect value (auto or length)
fn parse_clip_value(value : String) -> Double {
  let v = value.trim().to_lower()
  if v == "auto" {
    // auto in clip rect context means 0 (no offset)
    return 0.0
  }
  // Parse px value
  match v.strip_suffix("px") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string().trim()) catch {
        _ => return 0.0
      }
      return n
    }
    None => ()
  }
  // Parse plain number
  let n = @string.parse_double(v.to_string()) catch { _ => return 0.0 }
  n
}