///|
/// CSS Computed Values
/// Resolves cascaded values to computed values with inheritance

///|
/// Computed values context for resolving relative units
pub(all) struct ComputeContext {
  /// Parent's computed style (for inheritance)
  parent_style : @style.Style?
  /// Root font size (for rem units)
  root_font_size : Double
  /// Current font size (for em units)
  font_size : Double
  /// Viewport width (for vw units)
  viewport_width : Double
  /// Viewport height (for vh units)
  viewport_height : Double
  /// CSS Custom Properties (variables) - inherited from parent and local
  custom_properties : Map[String, String]
}

///|
fn view_to_string(v : StringView) -> String {
  let sb = StringBuilder::new()
  sb.write_stringview(v)
  sb.to_string()
}

///|
pub fn ComputeContext::new() -> ComputeContext {
  {
    parent_style: None,
    root_font_size: 16.0,
    font_size: 16.0,
    viewport_width: 1920.0,
    viewport_height: 1080.0,
    custom_properties: {},
  }
}

///|
fn ch_unit_ratio(ctx : ComputeContext) -> Double {
  match ctx.parent_style {
    Some(style) =>
      if style.font_family.to_lower().contains("ahem") {
        1.0
      } else {
        0.5
      }
    None => 0.5
  }
}

///|
pub fn ComputeContext::with_parent(parent : @style.Style) -> ComputeContext {
  {
    parent_style: Some(parent),
    root_font_size: 16.0,
    font_size: 16.0,
    viewport_width: 1920.0,
    viewport_height: 1080.0,
    custom_properties: {},
  }
}

///|
/// Create context with parent style and inherited custom properties
pub fn ComputeContext::with_parent_and_vars(
  parent : @style.Style,
  parent_vars : Map[String, String],
) -> ComputeContext {
  // Copy parent vars for inheritance (custom properties inherit by default)
  let vars : Map[String, String] = {}
  for k, v in parent_vars {
    vars[k] = v
  }
  {
    parent_style: Some(parent),
    root_font_size: 16.0,
    font_size: 16.0,
    viewport_width: 1920.0,
    viewport_height: 1080.0,
    custom_properties: vars,
  }
}

///|
/// Create context with custom viewport dimensions
pub fn ComputeContext::with_viewport(
  viewport_width : Double,
  viewport_height : Double,
) -> ComputeContext {
  {
    parent_style: None,
    root_font_size: 16.0,
    font_size: 16.0,
    viewport_width,
    viewport_height,
    custom_properties: {},
  }
}

///|
/// Mutable style builder for collecting computed values
priv struct StyleBuilder {
  mut display : @types.Display
  mut position : @types.Position
  mut float : @types.Float
  mut clear : @types.Clear
  mut box_sizing : @types.BoxSizing
  mut overflow_x : @types.Overflow
  mut overflow_y : @types.Overflow
  mut scroll_snap_type : @style.ScrollSnapType
  mut scroll_snap_align_x : @style.ScrollSnapAlign
  mut scroll_snap_align_y : @style.ScrollSnapAlign
  mut width : @types.Dimension
  mut height : @types.Dimension
  mut min_width : @types.Dimension
  mut min_height : @types.Dimension
  mut max_width : @types.Dimension
  mut max_height : @types.Dimension
  mut margin_top : @types.Dimension
  mut margin_right : @types.Dimension
  mut margin_bottom : @types.Dimension
  mut margin_left : @types.Dimension
  mut margin_trim : @style.MarginTrim
  mut padding_top : @types.Dimension
  mut padding_right : @types.Dimension
  mut padding_bottom : @types.Dimension
  mut padding_left : @types.Dimension
  mut border_top : @types.Dimension
  mut border_right : @types.Dimension
  mut border_bottom : @types.Dimension
  mut border_left : @types.Dimension
  mut border_style_top : @style.BorderStyle
  mut border_style_right : @style.BorderStyle
  mut border_style_bottom : @style.BorderStyle
  mut border_style_left : @style.BorderStyle
  mut border_color_top : @types.Color
  mut border_color_right : @types.Color
  mut border_color_bottom : @types.Color
  mut border_color_left : @types.Color
  mut flex_direction : @types.FlexDirection
  mut flex_wrap : @types.FlexWrap
  mut justify_content : @types.Alignment
  mut align_items : @types.Alignment
  mut align_content : @types.Alignment
  mut justify_content_unsafe : Bool
  mut align_content_unsafe : Bool
  mut align_self : @types.AlignSelf
  mut justify_self : @types.AlignSelf
  mut justify_items : @types.Alignment
  mut flex_grow : Double
  mut flex_shrink : Double
  mut flex_basis : @types.Dimension
  mut order : Int
  mut row_gap : @types.Dimension
  mut column_gap : @types.Dimension
  mut column_gap_is_normal : Bool
  mut column_count : Int?
  mut column_width : @types.Dimension
  mut column_fill : @style.ColumnFill
  mut aspect_ratio : Double?
  mut inset_top : @types.Dimension
  mut inset_right : @types.Dimension
  mut inset_bottom : @types.Dimension
  mut inset_left : @types.Dimension
  mut grid_auto_flow : @types.GridAutoFlow
  mut grid_template_columns : Array[@types.TrackSizingFunction]
  mut grid_template_rows : Array[@types.TrackSizingFunction]
  // Grid template areas (container)
  mut grid_template_areas : Array[String]
  // Grid item placement
  mut grid_column_start : @types.GridPlacement
  mut grid_column_end : @types.GridPlacement
  mut grid_row_start : @types.GridPlacement
  mut grid_row_end : @types.GridPlacement
  // Grid area name (item)
  mut grid_area : String?
  // Font properties
  mut font_size : Double
  mut font_weight : Double
  mut font_family : String
  mut line_height : Double
  // Text wrapping and direction
  mut text_align : @style.TextAlign
  mut white_space : @style.WhiteSpace
  mut text_overflow : @style.TextOverflow
  mut writing_mode : @style.WritingMode
  mut direction : @style.Direction
  // Vertical alignment for inline elements
  mut vertical_align : @style.VerticalAlign
  // Clip (legacy)
  mut clip : @types.ClipRect
  mut clip_path : @style.ClipPath
  // Paint properties
  mut visibility : @style.Visibility
  mut pointer_events : @style.PointerEvents
  mut z_index : @style.ZIndex
  mut opacity : Double
  // Color properties
  mut color : @types.Color
  mut background_color : @types.Color
  mut background_image : @types.BackgroundImage
  mut box_shadows : Array[@style.BoxShadow]
  // Containment
  mut contain : @style.Contain
  // Fragmentation
  mut break_before : @style.BreakBefore
  mut break_after : @style.BreakBefore
  mut break_inside : @style.BreakInside
  mut column_span : @style.ColumnSpan
  mut contain_intrinsic_inline_size : Double?
  mut contain_intrinsic_block_size : Double?
  // Transform
  mut transform : @style.Transform
  // Zoom (affects layout scaling)
  mut zoom : Double
  // Table properties
  mut border_spacing : Double
  mut border_spacing_vertical : Double
  mut border_collapse : @style.BorderCollapse
  mut table_layout : @style.TableLayout
  mut caption_side : @style.CaptionSide
  // Table cell properties (not mutable via CSS, set from HTML attributes)
  rowspan : Int
  colspan : Int
  // Border radius
  mut border_top_left_radius : Double
  mut border_top_right_radius : Double
  mut border_bottom_right_radius : Double
  mut border_bottom_left_radius : Double
  mut text_decoration_underline : Bool
  mut text_decoration_line_through : Bool
  mut text_decoration_overline : Bool
  mut letter_spacing : Double
  mut word_spacing : Double
}

///|
fn StyleBuilder::new() -> StyleBuilder {
  {
    display: @types.Display::Block,
    position: @types.Position::Static,
    float: @types.Float::None,
    clear: @types.Clear::None,
    box_sizing: @types.BoxSizing::ContentBox,
    overflow_x: @types.Overflow::Visible,
    overflow_y: @types.Overflow::Visible,
    scroll_snap_type: @style.ScrollSnapType::none(),
    scroll_snap_align_x: @style.ScrollSnapAlign::None,
    scroll_snap_align_y: @style.ScrollSnapAlign::None,
    width: @types.Dimension::Auto,
    height: @types.Dimension::Auto,
    min_width: @types.Dimension::Auto,
    min_height: @types.Dimension::Auto,
    max_width: @types.Dimension::Auto,
    max_height: @types.Dimension::Auto,
    margin_top: @types.Dimension::Length(0.0),
    margin_right: @types.Dimension::Length(0.0),
    margin_bottom: @types.Dimension::Length(0.0),
    margin_left: @types.Dimension::Length(0.0),
    margin_trim: @style.MarginTrim::None,
    padding_top: @types.Dimension::Length(0.0),
    padding_right: @types.Dimension::Length(0.0),
    padding_bottom: @types.Dimension::Length(0.0),
    padding_left: @types.Dimension::Length(0.0),
    border_top: @types.Dimension::Length(0.0),
    border_right: @types.Dimension::Length(0.0),
    border_bottom: @types.Dimension::Length(0.0),
    border_left: @types.Dimension::Length(0.0),
    border_style_top: @style.BorderStyle::None,
    border_style_right: @style.BorderStyle::None,
    border_style_bottom: @style.BorderStyle::None,
    border_style_left: @style.BorderStyle::None,
    border_color_top: @types.Color::transparent(),
    border_color_right: @types.Color::transparent(),
    border_color_bottom: @types.Color::transparent(),
    border_color_left: @types.Color::transparent(),
    flex_direction: @types.FlexDirection::Row,
    flex_wrap: @types.FlexWrap::NoWrap,
    justify_content: @types.Alignment::FlexStart,
    align_items: @types.Alignment::Stretch,
    align_content: @types.Alignment::Stretch,
    justify_content_unsafe: false,
    align_content_unsafe: false,
    align_self: @types.AlignSelf::Auto,
    justify_self: @types.AlignSelf::Auto,
    justify_items: @types.Alignment::Stretch,
    flex_grow: 0.0,
    flex_shrink: 1.0,
    flex_basis: @types.Dimension::Auto,
    order: 0,
    row_gap: @types.Dimension::Length(0.0),
    column_gap: @types.Dimension::Length(0.0),
    column_gap_is_normal: true,
    column_count: None,
    column_width: @types.Dimension::Auto,
    column_fill: @style.ColumnFill::Balance,
    aspect_ratio: None,
    inset_top: @types.Dimension::Auto,
    inset_right: @types.Dimension::Auto,
    inset_bottom: @types.Dimension::Auto,
    inset_left: @types.Dimension::Auto,
    grid_auto_flow: @types.GridAutoFlow::Row,
    grid_template_columns: [],
    grid_template_rows: [],
    grid_template_areas: [],
    grid_column_start: @types.GridPlacement::Auto,
    grid_column_end: @types.GridPlacement::Auto,
    grid_row_start: @types.GridPlacement::Auto,
    grid_row_end: @types.GridPlacement::Auto,
    grid_area: None,
    font_size: 16.0,
    font_weight: 400.0,
    font_family: "",
    line_height: 16.0,
    text_align: @style.TextAlign::Start,
    white_space: @style.WhiteSpace::Normal,
    text_overflow: @style.TextOverflow::Clip,
    writing_mode: @style.WritingMode::HorizontalTb,
    direction: @style.Direction::Ltr,
    vertical_align: @style.VerticalAlign::Baseline,
    clip: @types.ClipRect::Auto,
    clip_path: @style.ClipPath::none(),
    visibility: @style.Visibility::Visible,
    pointer_events: @style.PointerEvents::Auto,
    z_index: @style.ZIndex::Auto,
    opacity: 1.0,
    color: @types.Color::black(),
    background_color: @types.Color::transparent(),
    background_image: @types.None,
    box_shadows: [],
    contain: @style.Contain::none(),
    break_before: @style.BreakBefore::Auto,
    break_after: @style.BreakBefore::Auto,
    break_inside: @style.BreakInside::Auto,
    column_span: @style.ColumnSpan::None,
    contain_intrinsic_inline_size: None,
    contain_intrinsic_block_size: None,
    transform: @style.Transform::none(),
    zoom: 1.0,
    border_spacing: 0.0,
    border_spacing_vertical: 0.0,
    border_collapse: @style.BorderCollapse::Separate,
    table_layout: @style.TableLayout::Auto,
    caption_side: @style.CaptionSide::Top,
    rowspan: 1,
    colspan: 1,
    border_top_left_radius: 0.0,
    border_top_right_radius: 0.0,
    border_bottom_right_radius: 0.0,
    border_bottom_left_radius: 0.0,
    text_decoration_underline: false,
    text_decoration_line_through: false,
    text_decoration_overline: false,
    letter_spacing: 0.0,
    word_spacing: 0.0,
  }
}

///|
/// Create a StyleBuilder from an existing Style
fn StyleBuilder::from_style(style : @style.Style) -> StyleBuilder {
  {
    display: style.display,
    position: style.position,
    float: style.float,
    clear: style.clear,
    box_sizing: style.box_sizing,
    overflow_x: style.overflow_x,
    overflow_y: style.overflow_y,
    scroll_snap_type: style.scroll_snap_type,
    scroll_snap_align_x: style.scroll_snap_align_x,
    scroll_snap_align_y: style.scroll_snap_align_y,
    width: style.width,
    height: style.height,
    min_width: style.min_width,
    min_height: style.min_height,
    max_width: style.max_width,
    max_height: style.max_height,
    margin_top: style.margin.top,
    margin_right: style.margin.right,
    margin_bottom: style.margin.bottom,
    margin_left: style.margin.left,
    margin_trim: style.margin_trim,
    padding_top: style.padding.top,
    padding_right: style.padding.right,
    padding_bottom: style.padding.bottom,
    padding_left: style.padding.left,
    border_top: style.border.top,
    border_right: style.border.right,
    border_bottom: style.border.bottom,
    border_left: style.border.left,
    border_style_top: style.border_style.top,
    border_style_right: style.border_style.right,
    border_style_bottom: style.border_style.bottom,
    border_style_left: style.border_style.left,
    border_color_top: style.border_color.top,
    border_color_right: style.border_color.right,
    border_color_bottom: style.border_color.bottom,
    border_color_left: style.border_color.left,
    flex_direction: style.flex_direction,
    flex_wrap: style.flex_wrap,
    justify_content: style.justify_content,
    align_items: style.align_items,
    align_content: style.align_content,
    justify_content_unsafe: style.justify_content_unsafe,
    align_content_unsafe: style.align_content_unsafe,
    align_self: style.align_self,
    justify_self: style.justify_self,
    justify_items: style.justify_items,
    flex_grow: style.flex_grow,
    flex_shrink: style.flex_shrink,
    flex_basis: style.flex_basis,
    order: style.order,
    row_gap: style.row_gap,
    column_gap: style.column_gap,
    column_gap_is_normal: style.column_gap_is_normal,
    column_count: style.column_count,
    column_width: style.column_width,
    column_fill: style.column_fill,
    aspect_ratio: style.aspect_ratio,
    inset_top: style.inset.top,
    inset_right: style.inset.right,
    inset_bottom: style.inset.bottom,
    inset_left: style.inset.left,
    grid_auto_flow: style.grid_auto_flow,
    grid_template_columns: style.grid_template_columns,
    grid_template_rows: style.grid_template_rows,
    grid_template_areas: style.grid_template_areas,
    grid_column_start: style.grid_column.start,
    grid_column_end: style.grid_column.end,
    grid_row_start: style.grid_row.start,
    grid_row_end: style.grid_row.end,
    grid_area: style.grid_area,
    font_size: style.font_size,
    font_weight: style.font_weight,
    font_family: style.font_family,
    line_height: style.line_height,
    text_align: style.text_align,
    white_space: style.white_space,
    text_overflow: style.text_overflow,
    writing_mode: style.writing_mode,
    direction: style.direction,
    vertical_align: style.vertical_align,
    clip: style.clip,
    clip_path: style.clip_path,
    visibility: style.visibility,
    pointer_events: style.pointer_events,
    z_index: style.z_index,
    opacity: style.opacity,
    color: style.color,
    background_color: style.background_color,
    background_image: style.background_image,
    box_shadows: style.box_shadows,
    contain: style.contain,
    break_before: style.break_before,
    break_after: style.break_after,
    break_inside: style.break_inside,
    column_span: style.column_span,
    contain_intrinsic_inline_size: style.contain_intrinsic_inline_size,
    contain_intrinsic_block_size: style.contain_intrinsic_block_size,
    transform: style.transform,
    zoom: style.zoom,
    border_spacing: style.border_spacing,
    border_spacing_vertical: style.border_spacing_vertical,
    border_collapse: style.border_collapse,
    table_layout: style.table_layout,
    caption_side: style.caption_side,
    rowspan: style.rowspan,
    colspan: style.colspan,
    border_top_left_radius: style.border_top_left_radius,
    border_top_right_radius: style.border_top_right_radius,
    border_bottom_right_radius: style.border_bottom_right_radius,
    border_bottom_left_radius: style.border_bottom_left_radius,
    text_decoration_underline: style.text_decoration_underline,
    text_decoration_line_through: style.text_decoration_line_through,
    text_decoration_overline: style.text_decoration_overline,
    letter_spacing: style.letter_spacing,
    word_spacing: style.word_spacing,
  }
}

///|
fn StyleBuilder::build(self : StyleBuilder) -> @style.Style {
  {
    display: self.display,
    position: self.position,
    float: self.float,
    clear: self.clear,
    box_sizing: self.box_sizing,
    overflow_x: self.overflow_x,
    overflow_y: self.overflow_y,
    scroll_snap_type: self.scroll_snap_type,
    scroll_snap_align_x: self.scroll_snap_align_x,
    scroll_snap_align_y: self.scroll_snap_align_y,
    width: self.width,
    height: self.height,
    min_width: self.min_width,
    min_height: self.min_height,
    max_width: self.max_width,
    max_height: self.max_height,
    margin: {
      top: self.margin_top,
      right: self.margin_right,
      bottom: self.margin_bottom,
      left: self.margin_left,
    },
    margin_trim: self.margin_trim,
    padding: {
      top: self.padding_top,
      right: self.padding_right,
      bottom: self.padding_bottom,
      left: self.padding_left,
    },
    border: {
      top: self.border_top,
      right: self.border_right,
      bottom: self.border_bottom,
      left: self.border_left,
    },
    border_style: {
      top: self.border_style_top,
      right: self.border_style_right,
      bottom: self.border_style_bottom,
      left: self.border_style_left,
    },
    border_color: {
      top: self.border_color_top,
      right: self.border_color_right,
      bottom: self.border_color_bottom,
      left: self.border_color_left,
    },
    flex_direction: self.flex_direction,
    flex_wrap: self.flex_wrap,
    justify_content: self.justify_content,
    align_items: self.align_items,
    align_content: self.align_content,
    justify_content_unsafe: self.justify_content_unsafe,
    align_content_unsafe: self.align_content_unsafe,
    align_self: self.align_self,
    flex_grow: self.flex_grow,
    flex_shrink: self.flex_shrink,
    flex_basis: self.flex_basis,
    order: self.order,
    row_gap: self.row_gap,
    column_gap: self.column_gap,
    column_gap_is_normal: self.column_gap_is_normal,
    column_count: self.column_count,
    column_width: self.column_width,
    column_fill: self.column_fill,
    aspect_ratio: self.aspect_ratio,
    inset: {
      top: self.inset_top,
      right: self.inset_right,
      bottom: self.inset_bottom,
      left: self.inset_left,
    },
    grid_template_rows: self.grid_template_rows,
    grid_template_columns: self.grid_template_columns,
    grid_auto_rows: [],
    grid_auto_columns: [],
    grid_auto_flow: self.grid_auto_flow,
    grid_template_areas: self.grid_template_areas,
    grid_row: { start: self.grid_row_start, end: self.grid_row_end },
    grid_column: { start: self.grid_column_start, end: self.grid_column_end },
    grid_area: self.grid_area,
    justify_items: self.justify_items,
    justify_self: self.justify_self,
    font_size: self.font_size,
    font_weight: self.font_weight,
    font_family: self.font_family,
    line_height: self.line_height,
    text_align: self.text_align,
    white_space: self.white_space,
    text_overflow: self.text_overflow,
    writing_mode: self.writing_mode,
    direction: self.direction,
    vertical_align: self.vertical_align,
    clip: self.clip,
    clip_path: self.clip_path,
    visibility: self.visibility,
    pointer_events: self.pointer_events,
    z_index: self.z_index,
    opacity: self.opacity,
    color: self.color,
    background_color: self.background_color,
    background_image: self.background_image,
    box_shadows: self.box_shadows,
    contain: self.contain,
    break_before: self.break_before,
    break_after: self.break_after,
    break_inside: self.break_inside,
    column_span: self.column_span,
    contain_intrinsic_inline_size: self.contain_intrinsic_inline_size,
    contain_intrinsic_block_size: self.contain_intrinsic_block_size,
    transform: self.transform,
    zoom: self.zoom,
    border_spacing: self.border_spacing,
    border_spacing_vertical: self.border_spacing_vertical,
    border_collapse: self.border_collapse,
    table_layout: self.table_layout,
    caption_side: self.caption_side,
    rowspan: self.rowspan,
    colspan: self.colspan,
    border_top_left_radius: self.border_top_left_radius,
    border_top_right_radius: self.border_top_right_radius,
    border_bottom_right_radius: self.border_bottom_right_radius,
    border_bottom_left_radius: self.border_bottom_left_radius,
    text_decoration_underline: self.text_decoration_underline,
    text_decoration_line_through: self.text_decoration_line_through,
    text_decoration_overline: self.text_decoration_overline,
    letter_spacing: self.letter_spacing,
    word_spacing: self.word_spacing,
  }
}

///|
/// Resolve a CSS keyword value to actual value
fn resolve_keyword(
  property : String,
  value : @cascade.PropertyValue,
  ctx : ComputeContext,
) -> String {
  match value {
    @cascade.PropertyValue::Value(v) => v
    @cascade.PropertyValue::Inherit =>
      // Get value from parent, or use initial if no parent
      get_inherited_value(property, ctx)
    @cascade.PropertyValue::Initial => initial_value(property)
    @cascade.PropertyValue::Unset =>
      // For inherited properties, acts like inherit
      // For non-inherited properties, acts like initial
      if is_inherited(property) {
        get_inherited_value(property, ctx)
      } else {
        initial_value(property)
      }
    @cascade.PropertyValue::Revert =>
      // Revert to user-agent stylesheet (we use initial as fallback)
      initial_value(property)
    @cascade.PropertyValue::RevertLayer =>
      // Revert to previous cascade layer (we use initial as fallback)
      initial_value(property)
  }
}

///|
/// Get inherited value from parent or initial value
fn get_inherited_value(property : String, ctx : ComputeContext) -> String {
  match ctx.parent_style {
    Some(parent) => get_style_value_as_string(property, parent)
    None => initial_value(property)
  }
}

///|
/// Get a style property value as string representation
fn get_style_value_as_string(property : String, style : @style.Style) -> String {
  match property {
    "display" =>
      match style.display {
        @types.Display::Block => "block"
        @types.Display::Inline => "inline"
        @types.Display::InlineBlock => "inline-block"
        @types.Display::Flex => "flex"
        @types.Display::InlineFlex => "inline-flex"
        @types.Display::Grid => "grid"
        @types.Display::InlineGrid => "inline-grid"
        @types.Display::Table => "table"
        @types.Display::InlineTable => "inline-table"
        @types.Display::TableRow => "table-row"
        @types.Display::TableCell => "table-cell"
        @types.Display::TableCaption => "table-caption"
        @types.Display::TableRowGroup => "table-row-group"
        @types.Display::TableHeaderGroup => "table-header-group"
        @types.Display::TableFooterGroup => "table-footer-group"
        @types.Display::TableColumn => "table-column"
        @types.Display::TableColumnGroup => "table-column-group"
        @types.Display::None => "none"
        @types.Display::Contents => "contents"
        @types.Display::FlowRoot => "flow-root"
      }
    "position" =>
      match style.position {
        @types.Position::Static => "static"
        @types.Position::Relative => "relative"
        @types.Position::Absolute => "absolute"
        @types.Position::Fixed => "fixed"
      }
    "flex-direction" =>
      match style.flex_direction {
        @types.FlexDirection::Row => "row"
        @types.FlexDirection::RowReverse => "row-reverse"
        @types.FlexDirection::Column => "column"
        @types.FlexDirection::ColumnReverse => "column-reverse"
      }
    "flex-wrap" =>
      match style.flex_wrap {
        @types.FlexWrap::NoWrap => "nowrap"
        @types.FlexWrap::Wrap => "wrap"
        @types.FlexWrap::WrapReverse => "wrap-reverse"
      }
    "width" => dimension_to_string(style.width)
    "height" => dimension_to_string(style.height)
    "min-width" => dimension_to_string(style.min_width)
    "min-height" => dimension_to_string(style.min_height)
    "max-width" => dimension_to_string(style.max_width)
    "max-height" => dimension_to_string(style.max_height)
    "margin-top" => dimension_to_string(style.margin.top)
    "margin-right" => dimension_to_string(style.margin.right)
    "margin-bottom" => dimension_to_string(style.margin.bottom)
    "margin-left" => dimension_to_string(style.margin.left)
    "margin-trim" =>
      match style.margin_trim {
        @style.MarginTrim::None => "none"
        @style.MarginTrim::BlockStart => "block-start"
        @style.MarginTrim::BlockEnd => "block-end"
        @style.MarginTrim::Block => "block"
        @style.MarginTrim::InlineStart => "inline-start"
        @style.MarginTrim::InlineEnd => "inline-end"
        @style.MarginTrim::Inline => "inline"
      }
    "padding-top" => dimension_to_string(style.padding.top)
    "padding-right" => dimension_to_string(style.padding.right)
    "padding-bottom" => dimension_to_string(style.padding.bottom)
    "padding-left" => dimension_to_string(style.padding.left)
    "column-count" =>
      match style.column_count {
        Some(v) => v.to_string()
        None => "auto"
      }
    "column-width" => dimension_to_string(style.column_width)
    "column-fill" =>
      match style.column_fill {
        @style.ColumnFill::Balance => "balance"
        @style.ColumnFill::Auto => "auto"
      }
    "break-before" =>
      match style.break_before {
        @style.BreakBefore::Auto => "auto"
        @style.BreakBefore::Column => "column"
        @style.BreakBefore::Avoid => "avoid"
      }
    "break-after" =>
      match style.break_after {
        @style.BreakBefore::Auto => "auto"
        @style.BreakBefore::Column => "column"
        @style.BreakBefore::Avoid => "avoid"
      }
    "break-inside" | "page-break-inside" =>
      match style.break_inside {
        @style.BreakInside::Auto => "auto"
        @style.BreakInside::Avoid => "avoid"
      }
    "column-span" =>
      match style.column_span {
        @style.ColumnSpan::None => "none"
        @style.ColumnSpan::All => "all"
      }
    "flex-grow" => style.flex_grow.to_string()
    "flex-shrink" => style.flex_shrink.to_string()
    "flex-basis" => dimension_to_string(style.flex_basis)
    "color" => style.color.to_hex()
    "background-color" => style.background_color.to_hex()
    "caption-side" =>
      match style.caption_side {
        @style.CaptionSide::Top => "top"
        @style.CaptionSide::Bottom => "bottom"
      }
    "border-collapse" =>
      match style.border_collapse {
        @style.BorderCollapse::Separate => "separate"
        @style.BorderCollapse::Collapse => "collapse"
      }
    "border-spacing" =>
      if style.border_spacing == style.border_spacing_vertical {
        style.border_spacing.to_string() + "px"
      } else {
        style.border_spacing.to_string() +
        "px " +
        style.border_spacing_vertical.to_string() +
        "px"
      }
    "writing-mode" =>
      match style.writing_mode {
        @style.WritingMode::HorizontalTb => "horizontal-tb"
        @style.WritingMode::VerticalRl => "vertical-rl"
        @style.WritingMode::VerticalLr => "vertical-lr"
      }
    "direction" =>
      match style.direction {
        @style.Direction::Ltr => "ltr"
        @style.Direction::Rtl => "rtl"
      }
    "font-size" => style.font_size.to_string() + "px"
    "font-weight" => style.font_weight.to_string()
    "line-height" => style.line_height.to_string() + "px"
    "text-decoration" | "text-decoration-line" =>
      text_decoration_to_string(
        style.text_decoration_underline,
        style.text_decoration_line_through,
        style.text_decoration_overline,
      )
    "text-overflow" =>
      match style.text_overflow {
        @style.TextOverflow::Clip => "clip"
        @style.TextOverflow::Ellipsis => "ellipsis"
      }
    "text-align" =>
      match style.text_align {
        @style.TextAlign::Start => "start"
        @style.TextAlign::End => "end"
        @style.TextAlign::Left => "left"
        @style.TextAlign::Right => "right"
        @style.TextAlign::Center => "center"
        @style.TextAlign::Justify => "justify"
      }
    "white-space" =>
      match style.white_space {
        @style.WhiteSpace::Normal => "normal"
        @style.WhiteSpace::Nowrap => "nowrap"
        @style.WhiteSpace::Pre => "pre"
        @style.WhiteSpace::PreWrap => "pre-wrap"
        @style.WhiteSpace::PreLine => "pre-line"
      }
    "pointer-events" =>
      match style.pointer_events {
        @style.PointerEvents::Auto => "auto"
        @style.PointerEvents::None => "none"
      }
    "contain-intrinsic-inline-size" =>
      match style.contain_intrinsic_inline_size {
        Some(v) => v.to_string() + "px"
        None => "none"
      }
    "contain-intrinsic-block-size" =>
      match style.contain_intrinsic_block_size {
        Some(v) => v.to_string() + "px"
        None => "none"
      }
    _ => initial_value(property)
  }
}

///|
/// Convert dimension to string
fn dimension_to_string(dim : @types.Dimension) -> String {
  match dim {
    @types.Dimension::Auto => "auto"
    @types.Dimension::Length(n) =>
      if n == 0.0 {
        "0"
      } else {
        n.to_string() + "px"
      }
    @types.Dimension::Percent(n) => (n * 100.0).to_string() + "%"
    @types.Dimension::MinContent => "min-content"
    @types.Dimension::MaxContent => "max-content"
    @types.Dimension::FitContent(n) => "fit-content(" + n.to_string() + "px)"
  }
}

///|
fn text_decoration_to_string(
  underline : Bool,
  line_through : Bool,
  overline : Bool,
) -> String {
  let values : Array[String] = []
  if underline {
    values.push("underline")
  }
  if line_through {
    values.push("line-through")
  }
  if overline {
    values.push("overline")
  }
  if values.is_empty() {
    "none"
  } else {
    let buf = StringBuilder::new()
    for i, value in values {
      if i > 0 {
        buf.write_string(" ")
      }
      buf.write_string(value)
    }
    buf.to_string()
  }
}

///|
/// Compute a Style from cascaded values
pub fn compute(
  cascaded : @cascade.CascadedValues,
  ctx : ComputeContext,
) -> @style.Style {
  let builder = StyleBuilder::new()

  // First pass: collect custom properties (--*)
  // Custom properties are stored in the context for var() resolution
  cascaded.each(fn(prop, decl) {
    if prop.has_prefix("--") {
      let value = resolve_keyword(prop, decl.value, ctx)
      // Store custom property in context
      ctx.custom_properties[prop] = value
    }
  })

  seed_direction(builder, cascaded, ctx)
  // Seed writing-mode before applying logical properties.
  // Logical sizing must follow the element's effective writing mode.
  seed_writing_mode(builder, cascaded, ctx)
  // Seed font metrics before resolving em/ch/ex units in other properties.
  let font_ctx = seed_font_metrics(builder, cascaded, ctx)

  // Second pass: apply regular properties with var() resolution
  cascaded.each(fn(prop, decl) {
    if !prop.has_prefix("--") && !is_preseeded_font_property(prop) {
      let value = resolve_keyword(prop, decl.value, font_ctx)
      apply_property(builder, prop, value, font_ctx)
    }
  })

  // Handle inheritance for inherited properties
  apply_inheritance(builder, cascaded, ctx)
  builder.build()
}

///|
/// Compute a Style and return updated custom properties
/// Used when building a style tree to pass custom properties to children
pub fn compute_with_vars(
  cascaded : @cascade.CascadedValues,
  ctx : ComputeContext,
) -> (@style.Style, Map[String, String]) {
  let builder = StyleBuilder::new()

  // First pass: collect custom properties (--*)
  cascaded.each(fn(prop, decl) {
    if prop.has_prefix("--") {
      let value = resolve_keyword(prop, decl.value, ctx)
      ctx.custom_properties[prop] = value
    }
  })

  // Seed writing-mode before applying logical properties.
  seed_direction(builder, cascaded, ctx)
  seed_writing_mode(builder, cascaded, ctx)
  let font_ctx = seed_font_metrics(builder, cascaded, ctx)

  // Second pass: apply regular properties
  cascaded.each(fn(prop, decl) {
    if !prop.has_prefix("--") && !is_preseeded_font_property(prop) {
      let value = resolve_keyword(prop, decl.value, font_ctx)
      apply_property(builder, prop, value, font_ctx)
    }
  })

  // Handle inheritance
  apply_inheritance(builder, cascaded, ctx)

  // Return both the style and the custom properties for children
  (builder.build(), ctx.custom_properties)
}

///|
fn seed_direction(
  builder : StyleBuilder,
  cascaded : @cascade.CascadedValues,
  ctx : ComputeContext,
) -> Unit {
  match ctx.parent_style {
    Some(parent_style) => builder.direction = parent_style.direction
    None => ()
  }
  match cascaded.get("direction") {
    Some(decl) => {
      let value = resolve_keyword("direction", decl.value, ctx)
      builder.direction = parse_direction(value)
    }
    None => ()
  }
}

///|
/// Seed writing-mode on the builder before logical properties are applied.
/// Uses inherited writing-mode as default, then overrides with explicit declaration.
fn seed_writing_mode(
  builder : StyleBuilder,
  cascaded : @cascade.CascadedValues,
  ctx : ComputeContext,
) -> Unit {
  match ctx.parent_style {
    Some(parent_style) => builder.writing_mode = parent_style.writing_mode
    None => ()
  }
  match cascaded.get("writing-mode") {
    Some(decl) => {
      let value = resolve_keyword("writing-mode", decl.value, ctx)
      builder.writing_mode = parse_writing_mode(value)
    }
    None => ()
  }
}

///|
fn is_preseeded_font_property(property : String) -> Bool {
  property == "font" ||
  property == "font-size" ||
  property == "line-height" ||
  property == "font-weight" ||
  property == "font-family"
}

///|
/// Seed font-size/line-height ahead of general property application so
/// em/ch/ex units in width/height and gaps resolve against the element's font.
fn seed_font_metrics(
  builder : StyleBuilder,
  cascaded : @cascade.CascadedValues,
  ctx : ComputeContext,
) -> ComputeContext {
  let mut seeded_font_size = ctx.font_size
  let mut seeded_line_height = ctx.font_size * 1.2 // CSS initial: normal ≈ 1.2
  let mut seeded_font_weight = match ctx.parent_style {
    Some(parent_style) => parent_style.font_weight
    None => 400.0
  }
  let mut seeded_font_family = match ctx.parent_style {
    Some(parent_style) => parent_style.font_family
    None => ""
  }
  let mut font_decl : @cascade.Declaration? = None
  let mut font_size_decl : @cascade.Declaration? = None
  let mut line_height_decl : @cascade.Declaration? = None
  let mut font_weight_decl : @cascade.Declaration? = None
  let mut font_family_decl : @cascade.Declaration? = None
  match cascaded.get("font") {
    Some(decl) => font_decl = Some(decl)
    None => ()
  }
  match cascaded.get("font-size") {
    Some(decl) => font_size_decl = Some(decl)
    None => ()
  }
  match cascaded.get("line-height") {
    Some(decl) => line_height_decl = Some(decl)
    None => ()
  }
  match cascaded.get("font-weight") {
    Some(decl) => font_weight_decl = Some(decl)
    None => ()
  }
  match cascaded.get("font-family") {
    Some(decl) => font_family_decl = Some(decl)
    None => ()
  }
  for idx = 0; idx < 5; idx = idx + 1 {
    let mut next_prop = ""
    let mut next_decl : @cascade.Declaration? = None
    let mut next_order = 2147483647
    match font_decl {
      Some(decl) =>
        if decl.source_order < next_order {
          next_prop = "font"
          next_decl = Some(decl)
          next_order = decl.source_order
        }
      None => ()
    }
    match font_size_decl {
      Some(decl) =>
        if decl.source_order < next_order {
          next_prop = "font-size"
          next_decl = Some(decl)
          next_order = decl.source_order
        }
      None => ()
    }
    match line_height_decl {
      Some(decl) =>
        if decl.source_order < next_order {
          next_prop = "line-height"
          next_decl = Some(decl)
          next_order = decl.source_order
        }
      None => ()
    }
    match font_weight_decl {
      Some(decl) =>
        if decl.source_order < next_order {
          next_prop = "font-weight"
          next_decl = Some(decl)
          next_order = decl.source_order
        }
      None => ()
    }
    match font_family_decl {
      Some(decl) =>
        if decl.source_order < next_order {
          next_prop = "font-family"
          next_decl = Some(decl)
        }
      None => ()
    }
    if next_prop == "" {
      break
    }
    let decl = match next_decl {
      Some(decl) => decl
      None => break
    }
    let value = resolve_keyword(next_prop, decl.value, ctx)
    match next_prop {
      "font" => {
        let parsed = parse_font_shorthand(value, ctx)
        seeded_font_size = parsed.font_size
        seeded_line_height = parsed.line_height
        seeded_font_weight = parsed.font_weight
        seeded_font_family = parsed.font_family
        font_decl = None
      }
      "font-size" => {
        seeded_font_size = parse_font_size(value, ctx)
        font_size_decl = None
      }
      "line-height" => {
        seeded_line_height = parse_line_height(value, seeded_font_size)
        line_height_decl = None
      }
      "font-weight" => {
        seeded_font_weight = parse_font_weight(value)
        font_weight_decl = None
      }
      "font-family" => {
        seeded_font_family = parse_font_family(value)
        font_family_decl = None
      }
      _ => ()
    }
  }

  builder.font_size = seeded_font_size
  builder.line_height = seeded_line_height
  builder.font_weight = seeded_font_weight
  builder.font_family = seeded_font_family
  { ..ctx, font_size: seeded_font_size }
}

///|
/// Apply a resolved property value to a style builder
fn apply_property(
  builder : StyleBuilder,
  property : String,
  value : String,
  ctx : ComputeContext,
) -> Unit {
  match property {
    // Display and positioning
    "display" => builder.display = parse_display(value)
    "position" => builder.position = parse_position(value)
    "float" =>
      builder.float = parse_float_with_direction(value, builder.direction)
    "clear" =>
      builder.clear = parse_clear_with_direction(value, builder.direction)
    "box-sizing" =>
      builder.box_sizing = match value.trim().to_lower() {
        "border-box" => @types.BoxSizing::BorderBox
        "content-box" | "initial" | "inherit" => @types.BoxSizing::ContentBox
        _ => @types.BoxSizing::ContentBox
      }
    // Sizing
    "width" =>
      builder.width = resolve_dimension_with_calc_percent_fallback(value, ctx)
    "height" =>
      builder.height = resolve_dimension_with_calc_percent_fallback(value, ctx)
    "min-width" =>
      builder.min_width = resolve_dimension_with_mixed_calc_fallback(
        "min-width", value, ctx,
      )
    "min-height" =>
      builder.min_height = resolve_dimension_with_mixed_calc_fallback(
        "min-height", value, ctx,
      )
    "max-width" => builder.max_width = resolve_dimension(value, ctx)
    "max-height" => builder.max_height = resolve_dimension(value, ctx)
    // Logical sizing properties
    // In vertical writing-modes:
    // - inline axis is vertical -> inline-size maps to height
    // - block axis is horizontal -> block-size maps to width
    "inline-size" => {
      let dim = resolve_dimension_with_calc_percent_fallback(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.height = dim
      } else {
        builder.width = dim
      }
    }
    "block-size" => {
      let dim = resolve_dimension_with_calc_percent_fallback(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.width = dim
      } else {
        builder.height = dim
      }
    }
    "min-inline-size" => {
      let dim = resolve_dimension(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.min_height = dim
      } else {
        builder.min_width = dim
      }
    }
    "min-block-size" => {
      let dim = resolve_dimension(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.min_width = dim
      } else {
        builder.min_height = dim
      }
    }
    "max-inline-size" => {
      let dim = resolve_dimension(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.max_height = dim
      } else {
        builder.max_width = dim
      }
    }
    "max-block-size" => {
      let dim = resolve_dimension(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.max_width = dim
      } else {
        builder.max_height = dim
      }
    }
    // Box model
    "margin" => {
      // Parse margin shorthand: 1-4 values
      let parts : Array[StringView] = value
        .split(" ")
        .filter(fn(s) { !s.is_empty() })
        .collect()
      match parts.length() {
        1 => {
          let dim = resolve_dimension(parts[0].to_string(), ctx)
          builder.margin_top = dim
          builder.margin_right = dim
          builder.margin_bottom = dim
          builder.margin_left = dim
        }
        2 => {
          let tb = resolve_dimension(parts[0].to_string(), ctx)
          let lr = resolve_dimension(parts[1].to_string(), ctx)
          builder.margin_top = tb
          builder.margin_bottom = tb
          builder.margin_left = lr
          builder.margin_right = lr
        }
        3 => {
          builder.margin_top = resolve_dimension(parts[0].to_string(), ctx)
          let lr = resolve_dimension(parts[1].to_string(), ctx)
          builder.margin_left = lr
          builder.margin_right = lr
          builder.margin_bottom = resolve_dimension(parts[2].to_string(), ctx)
        }
        4 => {
          builder.margin_top = resolve_dimension(parts[0].to_string(), ctx)
          builder.margin_right = resolve_dimension(parts[1].to_string(), ctx)
          builder.margin_bottom = resolve_dimension(parts[2].to_string(), ctx)
          builder.margin_left = resolve_dimension(parts[3].to_string(), ctx)
        }
        _ => ()
      }
    }
    "margin-top" => builder.margin_top = resolve_dimension(value, ctx)
    "margin-right" => builder.margin_right = resolve_dimension(value, ctx)
    "margin-bottom" => builder.margin_bottom = resolve_dimension(value, ctx)
    "margin-left" => builder.margin_left = resolve_dimension(value, ctx)
    // Logical margin properties (LTR horizontal-tb writing mode)
    "margin-inline" => {
      let dim = resolve_dimension(value, ctx)
      builder.margin_left = dim
      builder.margin_right = dim
    }
    "margin-inline-start" => builder.margin_left = resolve_dimension(value, ctx)
    "margin-inline-end" => builder.margin_right = resolve_dimension(value, ctx)
    "margin-block" => {
      let dim = resolve_dimension(value, ctx)
      builder.margin_top = dim
      builder.margin_bottom = dim
    }
    "margin-block-start" => builder.margin_top = resolve_dimension(value, ctx)
    "margin-block-end" => builder.margin_bottom = resolve_dimension(value, ctx)
    "margin-trim" => builder.margin_trim = parse_margin_trim(value)
    "padding-top" => builder.padding_top = resolve_dimension(value, ctx)
    "padding-right" => builder.padding_right = resolve_dimension(value, ctx)
    "padding-bottom" => builder.padding_bottom = resolve_dimension(value, ctx)
    "padding-left" => builder.padding_left = resolve_dimension(value, ctx)
    "padding" => {
      // Shorthand: padding (1, 2, 3, or 4 values)
      let parts = value.trim().to_string().split(" ")
      let values : Array[@types.Dimension] = []
      for part in parts {
        let p = part.to_string().trim().to_string()
        if !p.is_empty() {
          values.push(resolve_dimension(p, ctx))
        }
      }
      match values.length() {
        1 => {
          builder.padding_top = values[0]
          builder.padding_right = values[0]
          builder.padding_bottom = values[0]
          builder.padding_left = values[0]
        }
        2 => {
          builder.padding_top = values[0]
          builder.padding_right = values[1]
          builder.padding_bottom = values[0]
          builder.padding_left = values[1]
        }
        3 => {
          builder.padding_top = values[0]
          builder.padding_right = values[1]
          builder.padding_bottom = values[2]
          builder.padding_left = values[1]
        }
        _ =>
          // 4 or more values
          if values.length() >= 4 {
            builder.padding_top = values[0]
            builder.padding_right = values[1]
            builder.padding_bottom = values[2]
            builder.padding_left = values[3]
          }
      }
    }
    // Logical padding properties (block/inline)
    "padding-block" => {
      let dim = resolve_dimension(value, ctx)
      builder.padding_top = dim
      builder.padding_bottom = dim
    }
    "padding-block-start" => builder.padding_top = resolve_dimension(value, ctx)
    "padding-block-end" =>
      builder.padding_bottom = resolve_dimension(value, ctx)
    "padding-inline" => {
      let dim = resolve_dimension(value, ctx)
      builder.padding_left = dim
      builder.padding_right = dim
    }
    "padding-inline-start" =>
      builder.padding_left = resolve_dimension(value, ctx)
    "padding-inline-end" =>
      builder.padding_right = resolve_dimension(value, ctx)
    "border-width" => {
      // Shorthand: border-width (1, 2, 3, or 4 values)
      let parts = value.trim().to_string().split(" ")
      let values : Array[@types.Dimension] = []
      for part in parts {
        let p = part.to_string().trim().to_string()
        if !p.is_empty() {
          values.push(resolve_dimension(p, ctx))
        }
      }
      if values.length() == 1 {
        // All sides
        builder.border_top = values[0]
        builder.border_right = values[0]
        builder.border_bottom = values[0]
        builder.border_left = values[0]
      } else if values.length() == 2 {
        // top/bottom, left/right
        builder.border_top = values[0]
        builder.border_bottom = values[0]
        builder.border_right = values[1]
        builder.border_left = values[1]
      } else if values.length() == 3 {
        // top, left/right, bottom
        builder.border_top = values[0]
        builder.border_right = values[1]
        builder.border_left = values[1]
        builder.border_bottom = values[2]
      } else if values.length() >= 4 {
        // top, right, bottom, left
        builder.border_top = values[0]
        builder.border_right = values[1]
        builder.border_bottom = values[2]
        builder.border_left = values[3]
      }
    }
    "border" => {
      // Shorthand: border: [width] [style] [color]
      let width = resolve_border_width(value, ctx)
      builder.border_top = width
      builder.border_right = width
      builder.border_bottom = width
      builder.border_left = width
      let bs = extract_border_style_from_shorthand(value)
      builder.border_style_top = bs
      builder.border_style_right = bs
      builder.border_style_bottom = bs
      builder.border_style_left = bs
      let bc = extract_border_color_from_shorthand(value, ctx)
      builder.border_color_top = bc
      builder.border_color_right = bc
      builder.border_color_bottom = bc
      builder.border_color_left = bc
    }
    "border-top" => {
      let bs = extract_border_style_from_shorthand(value)
      builder.border_style_top = bs
      builder.border_top = if is_none_border_style_enum(bs) {
        @types.Length(0.0)
      } else {
        resolve_border_width(value, ctx)
      }
      builder.border_color_top = extract_border_color_from_shorthand(value, ctx)
    }
    "border-right" => {
      let bs = extract_border_style_from_shorthand(value)
      builder.border_style_right = bs
      builder.border_right = if is_none_border_style_enum(bs) {
        @types.Length(0.0)
      } else {
        resolve_border_width(value, ctx)
      }
      builder.border_color_right = extract_border_color_from_shorthand(
        value, ctx,
      )
    }
    "border-bottom" => {
      let bs = extract_border_style_from_shorthand(value)
      builder.border_style_bottom = bs
      builder.border_bottom = if is_none_border_style_enum(bs) {
        @types.Length(0.0)
      } else {
        resolve_border_width(value, ctx)
      }
      builder.border_color_bottom = extract_border_color_from_shorthand(
        value, ctx,
      )
    }
    "border-left" => {
      let bs = extract_border_style_from_shorthand(value)
      builder.border_style_left = bs
      builder.border_left = if is_none_border_style_enum(bs) {
        @types.Length(0.0)
      } else {
        resolve_border_width(value, ctx)
      }
      builder.border_color_left = extract_border_color_from_shorthand(
        value, ctx,
      )
    }
    "border-top-width" => builder.border_top = resolve_dimension(value, ctx)
    "border-right-width" => builder.border_right = resolve_dimension(value, ctx)
    "border-bottom-width" =>
      builder.border_bottom = resolve_dimension(value, ctx)
    "border-left-width" => builder.border_left = resolve_dimension(value, ctx)
    "border-style" => {
      let raw_parts = value.trim().to_lower().split(" ")
      let parts : Array[String] = []
      for part in raw_parts {
        let p = part.to_string().trim().to_string()
        if !p.is_empty() {
          parts.push(p)
        }
      }
      if parts.length() == 1 {
        let s = parse_border_style_value(parts[0])
        builder.border_style_top = s
        builder.border_style_right = s
        builder.border_style_bottom = s
        builder.border_style_left = s
        if is_none_border_style(parts[0]) {
          builder.border_top = @types.Length(0.0)
          builder.border_right = @types.Length(0.0)
          builder.border_bottom = @types.Length(0.0)
          builder.border_left = @types.Length(0.0)
        }
      } else if parts.length() == 2 {
        let v = parse_border_style_value(parts[0])
        let h = parse_border_style_value(parts[1])
        builder.border_style_top = v
        builder.border_style_bottom = v
        builder.border_style_right = h
        builder.border_style_left = h
        if is_none_border_style(parts[0]) {
          builder.border_top = @types.Length(0.0)
          builder.border_bottom = @types.Length(0.0)
        }
        if is_none_border_style(parts[1]) {
          builder.border_right = @types.Length(0.0)
          builder.border_left = @types.Length(0.0)
        }
      } else if parts.length() >= 4 {
        builder.border_style_top = parse_border_style_value(parts[0])
        builder.border_style_right = parse_border_style_value(parts[1])
        builder.border_style_bottom = parse_border_style_value(parts[2])
        builder.border_style_left = parse_border_style_value(parts[3])
        if is_none_border_style(parts[0]) {
          builder.border_top = @types.Length(0.0)
        }
        if is_none_border_style(parts[1]) {
          builder.border_right = @types.Length(0.0)
        }
        if is_none_border_style(parts[2]) {
          builder.border_bottom = @types.Length(0.0)
        }
        if is_none_border_style(parts[3]) {
          builder.border_left = @types.Length(0.0)
        }
      }
    }
    "border-top-style" => {
      builder.border_style_top = parse_border_style_value(value)
      if is_none_border_style(value) {
        builder.border_top = @types.Length(0.0)
      }
    }
    "border-right-style" => {
      builder.border_style_right = parse_border_style_value(value)
      if is_none_border_style(value) {
        builder.border_right = @types.Length(0.0)
      }
    }
    "border-bottom-style" => {
      builder.border_style_bottom = parse_border_style_value(value)
      if is_none_border_style(value) {
        builder.border_bottom = @types.Length(0.0)
      }
    }
    "border-left-style" => {
      builder.border_style_left = parse_border_style_value(value)
      if is_none_border_style(value) {
        builder.border_left = @types.Length(0.0)
      }
    }
    "border-color" => {
      let c = parse_color_with_ctx(value, ctx).or_default(@types.Color::black())
      builder.border_color_top = c
      builder.border_color_right = c
      builder.border_color_bottom = c
      builder.border_color_left = c
    }
    "border-top-color" =>
      builder.border_color_top = parse_color_with_ctx(value, ctx).or_default(
        @types.Color::black(),
      )
    "border-right-color" =>
      builder.border_color_right = parse_color_with_ctx(value, ctx).or_default(
        @types.Color::black(),
      )
    "border-bottom-color" =>
      builder.border_color_bottom = parse_color_with_ctx(value, ctx).or_default(
        @types.Color::black(),
      )
    "border-left-color" =>
      builder.border_color_left = parse_color_with_ctx(value, ctx).or_default(
        @types.Color::black(),
      )
    // Logical border properties (block/inline)
    "border-block" | "border-block-width" => {
      let width = resolve_border_width(value, ctx)
      builder.border_top = width
      builder.border_bottom = width
    }
    "border-block-style" => {
      let raw_parts = value.trim().to_lower().split(" ")
      let parts : Array[String] = []
      for part in raw_parts {
        let p = part.to_string().trim().to_string()
        if !p.is_empty() {
          parts.push(p)
        }
      }
      if parts.length() > 0 && is_none_border_style(parts[0]) {
        builder.border_top = @types.Length(0.0)
      }
      if parts.length() > 1 && is_none_border_style(parts[1]) {
        builder.border_bottom = @types.Length(0.0)
      } else if parts.length() == 1 && is_none_border_style(parts[0]) {
        builder.border_bottom = @types.Length(0.0)
      }
    }
    "border-block-start" | "border-block-start-width" =>
      builder.border_top = resolve_border_width(value, ctx)
    "border-block-end" | "border-block-end-width" =>
      builder.border_bottom = resolve_border_width(value, ctx)
    "border-inline" | "border-inline-width" => {
      let width = resolve_border_width(value, ctx)
      if builder.writing_mode.is_vertical() {
        builder.border_top = width
        builder.border_bottom = width
      } else {
        builder.border_left = width
        builder.border_right = width
      }
    }
    "border-inline-style" => {
      let raw_parts = value.trim().to_lower().split(" ")
      let parts : Array[String] = []
      for part in raw_parts {
        let p = part.to_string().trim().to_string()
        if !p.is_empty() {
          parts.push(p)
        }
      }
      if builder.writing_mode.is_vertical() {
        if parts.length() > 0 && is_none_border_style(parts[0]) {
          builder.border_top = @types.Length(0.0)
        }
        if parts.length() > 1 && is_none_border_style(parts[1]) {
          builder.border_bottom = @types.Length(0.0)
        } else if parts.length() == 1 && is_none_border_style(parts[0]) {
          builder.border_bottom = @types.Length(0.0)
        }
      } else {
        if parts.length() > 0 && is_none_border_style(parts[0]) {
          builder.border_left = @types.Length(0.0)
        }
        if parts.length() > 1 && is_none_border_style(parts[1]) {
          builder.border_right = @types.Length(0.0)
        } else if parts.length() == 1 && is_none_border_style(parts[0]) {
          builder.border_right = @types.Length(0.0)
        }
      }
    }
    "border-inline-start" | "border-inline-start-width" =>
      if builder.writing_mode.is_vertical() {
        builder.border_top = resolve_border_width(value, ctx)
      } else {
        builder.border_left = resolve_border_width(value, ctx)
      }
    "border-inline-end" | "border-inline-end-width" =>
      if builder.writing_mode.is_vertical() {
        builder.border_bottom = resolve_border_width(value, ctx)
      } else {
        builder.border_right = resolve_border_width(value, ctx)
      }
    // Flexbox container
    "flex-direction" => builder.flex_direction = parse_flex_direction(value)
    "flex-wrap" => builder.flex_wrap = parse_flex_wrap(value)
    "justify-content" => {
      let (alignment, is_unsafe) = parse_alignment_with_overflow(value)
      builder.justify_content = alignment
      builder.justify_content_unsafe = is_unsafe
    }
    "align-items" => builder.align_items = parse_alignment(value)
    "align-content" => {
      let normalized = value.trim().to_lower().to_string()
      let (alignment, is_unsafe) = if normalized == "normal" {
        (@types.Alignment::Stretch, false)
      } else if normalized == "last baseline" {
        (@types.Alignment::Baseline, false)
      } else {
        parse_alignment_with_overflow(value)
      }
      builder.align_content = alignment
      builder.align_content_unsafe = is_unsafe
    }
    "place-content" =>
      match parse_place_content_with_overflow(value) {
        Some(((align_content, align_unsafe), (justify_content, justify_unsafe))) => {
          builder.align_content = align_content
          builder.align_content_unsafe = align_unsafe
          builder.justify_content = justify_content
          builder.justify_content_unsafe = justify_unsafe
        }
        None => ()
      }
    // Flexbox/Grid item alignment
    "align-self" => builder.align_self = parse_align_self(value)
    "justify-self" => builder.justify_self = parse_align_self(value)
    // Grid container alignment
    "justify-items" => builder.justify_items = parse_alignment(value)
    "flex-grow" => {
      let v = parse_number(value)
      if v >= 0.0 {
        builder.flex_grow = v
      }
    }
    "flex-shrink" => {
      let v = parse_number(value)
      if v >= 0.0 {
        builder.flex_shrink = v
      }
    }
    "flex-basis" => {
      // CSS spec: negative values are invalid, keep as auto
      let dim = resolve_dimension(value, ctx)
      match dim {
        @types.Length(v) => if v >= 0.0 { builder.flex_basis = dim }
        @types.Percent(v) => if v >= 0.0 { builder.flex_basis = dim }
        @types.Auto => builder.flex_basis = dim
        @types.MinContent => builder.flex_basis = dim
        @types.MaxContent => builder.flex_basis = dim
        @types.FitContent(_) => builder.flex_basis = dim
      }
    }
    "order" => builder.order = parse_integer(value)
    "flex" => {
      // Parse flex shorthand: none | auto |  [] []
      // Order must be: numbers first (grow, shrink), then optionally basis
      // Invalid orders like "1 0% 1" (basis in middle) must be rejected
      let v = value.trim()
      if v == "none" {
        builder.flex_grow = 0.0
        builder.flex_shrink = 0.0
        builder.flex_basis = @types.Dimension::Auto
      } else if v == "auto" {
        builder.flex_grow = 1.0
        builder.flex_shrink = 1.0
        builder.flex_basis = @types.Dimension::Auto
      } else if v == "initial" {
        builder.flex_grow = 0.0
        builder.flex_shrink = 1.0
        builder.flex_basis = @types.Dimension::Auto
      } else {
        // Try to parse as number(s) and optionally a basis
        let parts : Array[StringView] = v
          .split(" ")
          .filter(fn(s) { !s.is_empty() })
          .collect()
        // Validate ordering: numbers first, then basis
        // A value is a "number" if it's purely numeric (no % or units)
        // A value is a "basis" if it has %, px, or is "auto"/"content"
        let mut num_count = 0
        let mut basis_found = false
        let mut valid = true
        for i = 0; i < parts.length(); i = i + 1 {
          let p = parts[i].to_string()
          if is_flex_basis_value(p) {
            if basis_found {
              // Second basis value is invalid
              valid = false
            }
            basis_found = true
          } else if is_pure_number(p) {
            if basis_found {
              // Number after basis is invalid (e.g., "1 0% 1")
              valid = false
            }
            num_count = num_count + 1
          } else {
            // Unknown token
            valid = false
          }
        }
        if valid {
          match parts.length() {
            1 =>
              if num_count == 1 {
                // flex:  =  1 0%
                builder.flex_grow = parse_number(parts[0].to_string())
                builder.flex_shrink = 1.0
                builder.flex_basis = @types.Dimension::Percent(0.0)
              } else if basis_found {
                // flex:  = 1 1 
                builder.flex_grow = 1.0
                builder.flex_shrink = 1.0
                builder.flex_basis = resolve_dimension(
                  parts[0].to_string(),
                  ctx,
                )
              }
            2 =>
              if num_count == 2 {
                // flex:   =   0%
                builder.flex_grow = parse_number(parts[0].to_string())
                builder.flex_shrink = parse_number(parts[1].to_string())
                builder.flex_basis = @types.Dimension::Percent(0.0)
              } else if num_count == 1 && basis_found {
                // flex:  
                builder.flex_grow = parse_number(parts[0].to_string())
                builder.flex_shrink = 1.0
                builder.flex_basis = resolve_dimension(
                  parts[1].to_string(),
                  ctx,
                )
              }
            3 =>
              if num_count == 2 && basis_found {
                // flex:   
                builder.flex_grow = parse_number(parts[0].to_string())
                builder.flex_shrink = parse_number(parts[1].to_string())
                builder.flex_basis = resolve_dimension(
                  parts[2].to_string(),
                  ctx,
                )
              }
            // If num_count != 2 or basis not at end, it's invalid and we do nothing
            _ => ()
          }
        }
        // If not valid, we don't modify the builder (keep defaults)
      }
    }
    // flex-flow shorthand:  || 
    "flex-flow" => {
      let parts : Array[StringView] = value
        .trim()
        .split(" ")
        .filter(fn(s) { !s.is_empty() })
        .collect()
      for part in parts {
        let p = part.to_string()
        // Check flex-direction values
        if p == "row" {
          builder.flex_direction = @types.FlexDirection::Row
        } else if p == "row-reverse" {
          builder.flex_direction = @types.FlexDirection::RowReverse
        } else if p == "column" {
          builder.flex_direction = @types.FlexDirection::Column
        } else if p == "column-reverse" {
          builder.flex_direction = @types.FlexDirection::ColumnReverse
          // Check flex-wrap values
        } else if p == "nowrap" {
          builder.flex_wrap = @types.FlexWrap::NoWrap
        } else if p == "wrap" {
          builder.flex_wrap = @types.FlexWrap::Wrap
        } else if p == "wrap-reverse" {
          builder.flex_wrap = @types.FlexWrap::WrapReverse
        }
      }
    }
    // Gap (grid-gap is legacy alias)
    "gap" | "grid-gap" => {
      let parts : Array[StringView] = value
        .trim()
        .split(" ")
        .filter(fn(s) { !s.is_empty() })
        .collect()
      builder.column_gap_is_normal = false
      match parts.length() {
        1 => {
          let dim = resolve_dimension_with_calc_percent_fallback(
            parts[0].to_string(),
            ctx,
          )
          builder.row_gap = dim
          builder.column_gap = dim
        }
        2 => {
          builder.row_gap = resolve_dimension_with_calc_percent_fallback(
            parts[0].to_string(),
            ctx,
          )
          builder.column_gap = resolve_dimension_with_calc_percent_fallback(
            parts[1].to_string(),
            ctx,
          )
        }
        _ => {
          let dim = resolve_dimension_with_calc_percent_fallback(value, ctx)
          builder.row_gap = dim
          builder.column_gap = dim
        }
      }
    }
    "row-gap" | "grid-row-gap" =>
      builder.row_gap = resolve_dimension_with_calc_percent_fallback(value, ctx)
    "column-gap" | "grid-column-gap" => {
      builder.column_gap = resolve_dimension_with_calc_percent_fallback(
        value, ctx,
      )
      builder.column_gap_is_normal = false
    }
    // Multi-column
    "column-count" => builder.column_count = parse_column_count(value)
    "column-width" => builder.column_width = parse_column_width(value, ctx)
    "column-fill" => builder.column_fill = parse_column_fill(value)
    "columns" =>
      match parse_columns_shorthand(value, ctx) {
        Some((count, width)) => {
          builder.column_count = count
          builder.column_width = width
        }
        None => ()
      }
    "break-before" => builder.break_before = parse_break_before(value)
    "break-after" => builder.break_after = parse_break_before(value)
    "break-inside" | "page-break-inside" =>
      builder.break_inside = parse_break_inside(value)
    "column-span" => builder.column_span = parse_column_span(value)
    // Inset
    "top" => builder.inset_top = resolve_dimension(value, ctx)
    "right" => builder.inset_right = resolve_dimension(value, ctx)
    "bottom" => builder.inset_bottom = resolve_dimension(value, ctx)
    "left" => builder.inset_left = resolve_dimension(value, ctx)
    "inset" => {
      let parts : Array[StringView] = value
        .split(" ")
        .filter(fn(s) { !s.is_empty() })
        .collect()
      match parts.length() {
        1 => {
          let dim = resolve_dimension(parts[0].to_string(), ctx)
          builder.inset_top = dim
          builder.inset_right = dim
          builder.inset_bottom = dim
          builder.inset_left = dim
        }
        2 => {
          let tb = resolve_dimension(parts[0].to_string(), ctx)
          let lr = resolve_dimension(parts[1].to_string(), ctx)
          builder.inset_top = tb
          builder.inset_bottom = tb
          builder.inset_left = lr
          builder.inset_right = lr
        }
        3 => {
          builder.inset_top = resolve_dimension(parts[0].to_string(), ctx)
          let lr = resolve_dimension(parts[1].to_string(), ctx)
          builder.inset_left = lr
          builder.inset_right = lr
          builder.inset_bottom = resolve_dimension(parts[2].to_string(), ctx)
        }
        4 => {
          builder.inset_top = resolve_dimension(parts[0].to_string(), ctx)
          builder.inset_right = resolve_dimension(parts[1].to_string(), ctx)
          builder.inset_bottom = resolve_dimension(parts[2].to_string(), ctx)
          builder.inset_left = resolve_dimension(parts[3].to_string(), ctx)
        }
        _ => ()
      }
    }
    // Logical inset properties (LTR horizontal-tb writing mode)
    "inset-inline" => {
      let dim = resolve_dimension(value, ctx)
      builder.inset_left = dim
      builder.inset_right = dim
    }
    "inset-inline-start" => builder.inset_left = resolve_dimension(value, ctx)
    "inset-inline-end" => builder.inset_right = resolve_dimension(value, ctx)
    "inset-block" => {
      let dim = resolve_dimension(value, ctx)
      builder.inset_top = dim
      builder.inset_bottom = dim
    }
    "inset-block-start" => builder.inset_top = resolve_dimension(value, ctx)
    "inset-block-end" => builder.inset_bottom = resolve_dimension(value, ctx)
    // Aspect ratio
    "aspect-ratio" => builder.aspect_ratio = parse_aspect_ratio(value)
    // Overflow
    "overflow" => {
      let ov = parse_overflow(value)
      builder.overflow_x = ov
      builder.overflow_y = ov
    }
    "overflow-x" => builder.overflow_x = parse_overflow(value)
    "overflow-y" => builder.overflow_y = parse_overflow(value)
    "scroll-snap-type" =>
      builder.scroll_snap_type = parse_scroll_snap_type(value)
    "scroll-snap-align" => {
      let (inline_align, block_align) = parse_scroll_snap_align(value)
      builder.scroll_snap_align_x = inline_align
      builder.scroll_snap_align_y = block_align
    }
    // Grid container
    "grid-auto-flow" => builder.grid_auto_flow = parse_grid_auto_flow(value)
    "grid-template-columns" =>
      builder.grid_template_columns = parse_grid_template_tracks(value)
    "grid-template-rows" =>
      builder.grid_template_rows = parse_grid_template_tracks(value)
    "grid-template-areas" =>
      builder.grid_template_areas = parse_grid_template_areas_value(value)
    "grid-template" =>
      match parse_grid_template_shorthand(value) {
        Some((rows, cols)) => {
          builder.grid_template_rows = rows
          builder.grid_template_columns = cols
        }
        None => ()
      }
    "grid" =>
      match parse_grid_template_shorthand(value) {
        Some((rows, cols)) => {
          builder.grid_template_rows = rows
          builder.grid_template_columns = cols
        }
        None => ()
      }
    // Grid item placement
    "grid-column-start" =>
      builder.grid_column_start = parse_grid_placement(value)
    "grid-column-end" => builder.grid_column_end = parse_grid_placement(value)
    "grid-row-start" => builder.grid_row_start = parse_grid_placement(value)
    "grid-row-end" => builder.grid_row_end = parse_grid_placement(value)
    "grid-column" => {
      let (start, end) = parse_grid_line_shorthand(value)
      builder.grid_column_start = start
      builder.grid_column_end = end
    }
    "grid-row" => {
      let (start, end) = parse_grid_line_shorthand(value)
      builder.grid_row_start = start
      builder.grid_row_end = end
    }
    "grid-area" => {
      let v = value.trim().to_string()
      // Check if this is a single named area identifier (no slashes, not a number, not auto/span)
      if !v.contains("/") && is_grid_area_name(v) {
        builder.grid_area = Some(v)
      } else {
        let (row_start, column_start, row_end, column_end) = parse_grid_area_shorthand(
          value,
        )
        builder.grid_row_start = row_start
        builder.grid_column_start = column_start
        builder.grid_row_end = row_end
        builder.grid_column_end = column_end
      }
    }
    // Font properties
    "font-size" => builder.font_size = parse_font_size(value, ctx)
    "line-height" =>
      builder.line_height = parse_line_height(value, builder.font_size)
    "text-align" => builder.text_align = parse_text_align(value)
    "white-space" => builder.white_space = parse_white_space(value)
    "writing-mode" => builder.writing_mode = parse_writing_mode(value)
    "direction" => builder.direction = parse_direction(value)
    "vertical-align" => builder.vertical_align = parse_vertical_align(value)
    "font" => {
      // Parse font shorthand: [style] [variant] [weight] size[/line-height] family
      let parsed = parse_font_shorthand(value, ctx)
      builder.font_size = parsed.font_size
      builder.line_height = parsed.line_height
      builder.font_weight = parsed.font_weight
      builder.font_family = parsed.font_family
    }
    // CSS Containment
    "contain" => builder.contain = parse_contain(value)
    "container-type" =>
      match parse_container_type(value) {
        Some(implied) =>
          if implied.has_containment() {
            builder.contain = merge_contain(builder.contain, implied)
          }
        None => ()
      }
    "contain-intrinsic-inline-size" =>
      builder.contain_intrinsic_inline_size = parse_contain_intrinsic_axis(
        value, ctx,
      )
    "contain-intrinsic-block-size" =>
      builder.contain_intrinsic_block_size = parse_contain_intrinsic_axis(
        value, ctx,
      )
    "contain-intrinsic-size" =>
      match parse_contain_intrinsic_size(value, ctx) {
        Some((inline_size, block_size)) => {
          builder.contain_intrinsic_inline_size = inline_size
          builder.contain_intrinsic_block_size = block_size
        }
        None => ()
      }
    // Clip (legacy, deprecated but widely used for a11y)
    "clip" => builder.clip = parse_clip(value)
    "clip-path" => builder.clip_path = parse_clip_path(value)
    // Paint properties (visual, not layout)
    "visibility" => builder.visibility = parse_visibility(value)
    "pointer-events" => builder.pointer_events = parse_pointer_events(value)
    "z-index" => builder.z_index = parse_z_index(value)
    "opacity" => builder.opacity = parse_opacity(value)
    // Color properties
    "color" => {
      let parsed = parse_color_with_ctx(value, ctx)
      match parsed {
        Resolved(c) => builder.color = c
        CurrentColor =>
          // currentColor for 'color' property inherits from parent
          match ctx.parent_style {
            Some(parent) => builder.color = parent.color
            None => () // Keep default (black)
          }
        Inherit =>
          match ctx.parent_style {
            Some(parent) => builder.color = parent.color
            None => ()
          }
      }
    }
    "background-color" | "background" => {
      // Check for linear-gradient first
      let v_lower = value.trim().to_lower()
      if v_lower.contains("linear-gradient") {
        let grad = parse_linear_gradient(value)
        builder.background_image = grad
      } else {
        let parsed = parse_color_with_ctx(value, ctx)
        match parsed {
          Resolved(c) => builder.background_color = c
          CurrentColor =>
            // currentColor uses the element's computed color
            builder.background_color = builder.color
          Inherit =>
            match ctx.parent_style {
              Some(parent) => builder.background_color = parent.background_color
              None => ()
            }
        }
      }
    }
    "background-image" => {
      let v_lower = value.trim().to_lower()
      if v_lower.contains("linear-gradient") {
        builder.background_image = parse_linear_gradient(value)
      }
    }
    "box-shadow" =>
      builder.box_shadows = parse_box_shadows(value, ctx, builder.color)
    // Font weight
    "font-weight" => builder.font_weight = parse_font_weight(value)
    // Font family (store first family name for font matching)
    "font-family" => builder.font_family = parse_font_family(value)
    // Transform (visual position offset)
    "transform" => builder.transform = parse_transform(value)
    // Zoom (layout scaling, inherited)
    "zoom" => builder.zoom = parse_zoom(value)
    // Table properties
    "border-spacing" => {
      let (horizontal, vertical) = parse_border_spacing(value, ctx)
      builder.border_spacing = horizontal
      builder.border_spacing_vertical = vertical
    }
    "border-collapse" => builder.border_collapse = parse_border_collapse(value)
    "table-layout" => builder.table_layout = parse_table_layout(value)
    "caption-side" => builder.caption_side = parse_caption_side(value)
    // Border radius
    "border-radius" => {
      let r = resolve_border_radius(value, ctx)
      builder.border_top_left_radius = r.0
      builder.border_top_right_radius = r.1
      builder.border_bottom_right_radius = r.2
      builder.border_bottom_left_radius = r.3
    }
    "border-top-left-radius" =>
      builder.border_top_left_radius = resolve_dimension_to_px(
        resolve_dimension(value, ctx),
      )
    "border-top-right-radius" =>
      builder.border_top_right_radius = resolve_dimension_to_px(
        resolve_dimension(value, ctx),
      )
    "border-bottom-right-radius" =>
      builder.border_bottom_right_radius = resolve_dimension_to_px(
        resolve_dimension(value, ctx),
      )
    "border-bottom-left-radius" =>
      builder.border_bottom_left_radius = resolve_dimension_to_px(
        resolve_dimension(value, ctx),
      )
    "text-decoration" | "text-decoration-line" => {
      let v = value.trim().to_lower()
      builder.text_decoration_underline = v.contains("underline")
      builder.text_decoration_line_through = v.contains("line-through")
      builder.text_decoration_overline = v.contains("overline")
    }
    "text-overflow" =>
      builder.text_overflow = match value.trim().to_lower() {
        "ellipsis" => @style.TextOverflow::Ellipsis
        _ => @style.TextOverflow::Clip
      }
    "letter-spacing" => {
      let v = value.trim().to_lower()
      if v == "normal" {
        builder.letter_spacing = 0.0
      } else {
        builder.letter_spacing = resolve_dimension_to_px(
          resolve_dimension(value, ctx),
        )
      }
    }
    "word-spacing" => {
      let v = value.trim().to_lower()
      if v == "normal" {
        builder.word_spacing = 0.0
      } else {
        builder.word_spacing = resolve_dimension_to_px(
          resolve_dimension(value, ctx),
        )
      }
    }
    _ => ()
  }
}

///|
fn parse_font_weight(value : String) -> Double {
  match value.trim().to_lower() {
    "normal" => 400.0
    "bold" => 700.0
    "lighter" => 100.0
    "bolder" => 700.0
    _ => {
      let n = @string.parse_double(value.trim()) catch { _ => return 400.0 }
      if n >= 1.0 && n <= 1000.0 {
        n
      } else {
        400.0
      }
    }
  }
}

///|
/// Parse font-family, returning the first family name (lowercase, unquoted).
fn parse_font_family(value : String) -> String {
  let v = value.trim()
  // Split by comma, take first
  let first = match v.split(",").collect() {
    [] => v
    parts => parts[0]
  }
  // Remove quotes and normalize
  let trimmed = first.trim()
  let len = trimmed.length()
  if len >= 2 && trimmed[0] == '"' && trimmed[len - 1] == '"' {
    trimmed[1:len - 1].to_string().to_lower()
  } else if len >= 2 && trimmed[0] == '\'' && trimmed[len - 1] == '\'' {
    trimmed[1:len - 1].to_string().to_lower()
  } else {
    trimmed.to_string().to_lower()
  }
}

///|
fn split_css_top_level(input : String, delimiter : Char) -> Array[String] {
  let parts : Array[String] = []
  let current = StringBuilder::new()
  let mut depth = 0
  for ch in input {
    if ch == '(' {
      depth += 1
      current.write_char(ch)
      continue
    }
    if ch == ')' {
      if depth > 0 {
        depth -= 1
      }
      current.write_char(ch)
      continue
    }
    if ch == delimiter && depth == 0 {
      let part = current.to_string().trim().to_string()
      if !part.is_empty() {
        parts.push(part)
      }
      current.reset()
      continue
    }
    current.write_char(ch)
  }
  let last = current.to_string().trim().to_string()
  if !last.is_empty() {
    parts.push(last)
  }
  parts
}

///|
fn split_css_whitespace_preserving_functions(input : String) -> Array[String] {
  let parts : Array[String] = []
  let current = StringBuilder::new()
  let mut depth = 0
  for ch in input {
    if ch == '(' {
      depth += 1
      current.write_char(ch)
      continue
    }
    if ch == ')' {
      if depth > 0 {
        depth -= 1
      }
      current.write_char(ch)
      continue
    }
    let is_space = ch == ' ' || ch == '\t' || ch == '\n'
    if is_space && depth == 0 {
      let part = current.to_string().trim().to_string()
      if !part.is_empty() {
        parts.push(part)
      }
      current.reset()
      continue
    }
    current.write_char(ch)
  }
  let last = current.to_string().trim().to_string()
  if !last.is_empty() {
    parts.push(last)
  }
  parts
}

///|
fn parse_box_shadows(
  value : String,
  ctx : ComputeContext,
  default_color : @types.Color,
) -> Array[@style.BoxShadow] {
  let trimmed = value.trim().to_lower()
  if trimmed == "none" {
    return []
  }
  let shadows : Array[@style.BoxShadow] = []
  for entry in split_css_top_level(value, ',') {
    match parse_box_shadow(entry, ctx, default_color) {
      Some(shadow) => shadows.push(shadow)
      None => ()
    }
  }
  shadows
}

///|
fn parse_box_shadow(
  value : String,
  ctx : ComputeContext,
  default_color : @types.Color,
) -> @style.BoxShadow? {
  let tokens = split_css_whitespace_preserving_functions(value)
  if tokens.length() == 0 {
    return None
  }

  let mut inset = false
  let mut color = default_color
  let length_tokens : Array[String] = []
  for token in tokens {
    let lower = token.to_lower()
    if lower == "inset" {
      inset = true
      continue
    }
    match parse_css_value_to_px(token, ctx) {
      Some(_) => {
        length_tokens.push(token)
        continue
      }
      None => ()
    }
    if is_box_shadow_color_token(lower) {
      let parsed_color = parse_color_with_ctx(token, ctx)
      match parsed_color {
        Resolved(resolved) => {
          color = resolved
          continue
        }
        CurrentColor => {
          color = default_color
          continue
        }
        Inherit => return None
      }
    }
    return None
  }

  if length_tokens.length() < 2 || length_tokens.length() > 4 {
    return None
  }

  let lengths : Array[Double] = []
  for token in length_tokens {
    match parse_css_value_to_px(token, ctx) {
      Some(px) => lengths.push(px)
      None => return None
    }
  }

  Some(
    @style.BoxShadow::new(
      inset,
      lengths[0],
      lengths[1],
      if lengths.length() >= 3 {
        @cmp.maximum(lengths[2], 0.0)
      } else {
        0.0
      },
      if lengths.length() >= 4 {
        lengths[3]
      } else {
        0.0
      },
      color,
    ),
  )
}

///|
fn is_box_shadow_color_token(token : String) -> Bool {
  token.has_prefix("#") ||
  token.has_prefix("rgb(") ||
  token.has_prefix("rgba(") ||
  token == "transparent" ||
  token == "currentcolor" ||
  get_named_color(token) is Some(_)
}

///|
/// Parse border-collapse value
fn parse_border_collapse(value : String) -> @style.BorderCollapse {
  match value.trim() {
    "collapse" => @style.Collapse
    "separate" => @style.Separate
    _ => @style.Separate
  }
}

///|
/// Border style keywords that suppress used border width.
fn is_none_border_style_enum(style : @style.BorderStyle) -> Bool {
  style == @style.BorderStyle::None || style == @style.BorderStyle::Hidden
}

///|
fn is_none_border_style(value : String) -> Bool {
  let v = value.trim().to_lower()
  v == "none" || v == "hidden"
}

///|
fn extract_border_color_from_shorthand(
  value : String,
  ctx : ComputeContext,
) -> @types.Color {
  let parts = split_whitespace(value)
  for part in parts {
    let lower = part.to_lower()
    // Skip border-style keywords and width keywords/values
    match lower {
      "none"
      | "hidden"
      | "dotted"
      | "dashed"
      | "solid"
      | "double"
      | "groove"
      | "ridge"
      | "inset"
      | "outset"
      | "thin"
      | "medium"
      | "thick" => continue
      _ => ()
    }
    // Skip numeric values (border width)
    if lower.length() > 0 && lower[0] >= '0' && lower[0] <= '9' {
      continue
    }
    if lower == "0" {
      continue
    }
    // Try to parse as color
    let color_val = parse_color_with_ctx(part, ctx)
    match color_val.get_color() {
      Some(c) => return c
      None => ()
    }
  }
  @types.Color::black() // CSS default: currentColor → approximate as black
}

///|
fn extract_border_style_from_shorthand(value : String) -> @style.BorderStyle {
  let parts = split_whitespace(value)
  let mut found_style = false
  let mut result = @style.BorderStyle::None
  for part in parts {
    let lower = part.to_lower()
    match lower {
      "none"
      | "hidden"
      | "dotted"
      | "dashed"
      | "solid"
      | "double"
      | "groove"
      | "ridge"
      | "inset"
      | "outset" => {
        found_style = true
        result = parse_border_style_value(part)
      }
      _ => ()
    }
  }
  if found_style {
    result
  } else {
    @style.BorderStyle::Solid // Default when only width/color specified
  }
}

///|
fn parse_border_style_value(value : String) -> @style.BorderStyle {
  match value.trim().to_lower() {
    "none" => @style.BorderStyle::None
    "hidden" => @style.BorderStyle::Hidden
    "dotted" => @style.BorderStyle::Dotted
    "dashed" => @style.BorderStyle::Dashed
    "solid" => @style.BorderStyle::Solid
    "double" => @style.BorderStyle::Double
    "groove" => @style.BorderStyle::Groove
    "ridge" => @style.BorderStyle::Ridge
    "inset" => @style.BorderStyle::Inset
    "outset" => @style.BorderStyle::Outset
    _ => @style.BorderStyle::None
  }
}

///|
/// Resolve border width from width/style/color shorthand tokens.
/// Falls back to the existing default 1.5px for style-only shorthands.
fn resolve_border_width(
  value : String,
  ctx : ComputeContext,
) -> @types.Dimension {
  let v = value.trim().to_lower()
  if v == "none" || v == "hidden" || v == "0" {
    return @types.Length(0.0)
  }

  let mut has_visible_style = false
  let mut has_none_style = false

  let parts = v
    .split(" ")
    .map(fn(part) { part.to_string().trim().to_string() })
    .filter(fn(part) { !part.is_empty() })
    .collect()

  for part in parts {
    match part {
      "none" | "hidden" => {
        has_none_style = true
        continue
      }
      "thin" => return @types.Length(1.0)
      "medium" => return @types.Length(3.0)
      "thick" => return @types.Length(5.0)
      "solid"
      | "dotted"
      | "dashed"
      | "double"
      | "groove"
      | "ridge"
      | "inset"
      | "outset" => {
        has_visible_style = true
        continue
      }
      _ => ()
    }

    // Length-like token found in shorthand.
    match resolve_dimension(part, ctx) {
      @types.Length(px) => return @types.Length(px)
      _ => ()
    }
  }

  if has_none_style {
    @types.Length(0.0)
  } else if has_visible_style {
    @types.Length(1.5)
  } else {
    @types.Length(0.0)
  }
}

///|
/// Parse table-layout value
fn parse_table_layout(value : String) -> @style.TableLayout {
  match value.trim() {
    "fixed" => @style.TableLayout::Fixed
    "auto" => @style.Auto
    _ => @style.Auto
  }
}

///|
/// Parse caption-side value
fn parse_caption_side(value : String) -> @style.CaptionSide {
  match value.trim() {
    "top" => @style.CaptionSide::Top
    "bottom" => @style.CaptionSide::Bottom
    _ => @style.CaptionSide::Top
  }
}

///|
/// Parse margin-trim value
fn parse_margin_trim(value : String) -> @style.MarginTrim {
  match value.trim().to_lower() {
    "none" => @style.MarginTrim::None
    "block-start" => @style.MarginTrim::BlockStart
    "block-end" => @style.MarginTrim::BlockEnd
    "block" => @style.MarginTrim::Block
    "inline-start" => @style.MarginTrim::InlineStart
    "inline-end" => @style.MarginTrim::InlineEnd
    "inline" => @style.MarginTrim::Inline
    _ => @style.MarginTrim::None
  }
}

///|
/// Parse border-spacing value (single value or two values)
fn parse_border_spacing(
  value : String,
  ctx : ComputeContext,
) -> (Double, Double) {
  let v = value.trim()
  // Split by whitespace
  let parts : Array[StringView] = v
    .split(" ")
    .filter(fn(s) { !s.is_empty() })
    .collect()
  if parts.length() == 0 {
    return (0.0, 0.0)
  }
  let horizontal = match resolve_dimension(parts[0].to_string(), ctx) {
    @types.Length(px) => px
    _ => 0.0
  }
  let vertical = if parts.length() >= 2 {
    match resolve_dimension(parts[1].to_string(), ctx) {
      @types.Length(px) => px
      _ => horizontal
    }
  } else {
    horizontal
  }
  (horizontal, vertical)
}

///|
/// Parse border-radius shorthand (1-4 values)
/// Returns (top-left, top-right, bottom-right, bottom-left) in px
fn resolve_border_radius(
  value : String,
  ctx : ComputeContext,
) -> (Double, Double, Double, Double) {
  let v = value.trim()
  // Handle slash syntax (horizontal / vertical) - take only horizontal for now
  let effective = if v.contains("/") {
    let slash_parts : Array[StringView] = v.split("/").collect()
    slash_parts[0].to_string().trim()
  } else {
    v
  }
  let parts : Array[StringView] = effective
    .split(" ")
    .filter(fn(s) { !s.is_empty() })
    .collect()
  if parts.length() == 0 {
    return (0.0, 0.0, 0.0, 0.0)
  }
  let values : Array[Double] = parts.map(fn(p) {
    let dim = resolve_dimension(p.to_string(), ctx)
    match dim {
      @types.Percent(pct) => -pct // Negative encodes percentage (fraction) for render-time resolution
      _ => resolve_dimension_to_px(dim)
    }
  })
  match values.length() {
    1 => (values[0], values[0], values[0], values[0])
    2 => (values[0], values[1], values[0], values[1])
    3 => (values[0], values[1], values[2], values[1])
    _ => (values[0], values[1], values[2], values[3])
  }
}

///|
/// Parse a value inside CSS math functions and resolve it to a dimension
/// Handles recursively nested calc(), min(), max(), clamp()
fn parse_css_value_to_px(value : String, ctx : ComputeContext) -> Double? {
  let v = value.trim()
  if v.is_empty() {
    return None
  }

  // Handle nested calc()
  if v.has_prefix("calc(") && v.has_suffix(")") {
    let inner = view_to_string(
      v.view(start_offset=5, end_offset=v.length() - 1),
    )
    return parse_simple_calc_expr(inner, ctx)
  }

  // Handle nested min()
  if v.has_prefix("min(") && v.has_suffix(")") {
    let inner = view_to_string(
      v.view(start_offset=4, end_offset=v.length() - 1),
    )
    let args = split_css_args(inner)
    let mut min_val : Double? = None
    for arg in args {
      match parse_css_value_to_px(arg, ctx) {
        Some(val) =>
          match min_val {
            None => min_val = Some(val)
            Some(current) => if val < current { min_val = Some(val) }
          }
        None => return None
      }
    }
    return min_val
  }

  // Handle nested max()
  if v.has_prefix("max(") && v.has_suffix(")") {
    let inner = view_to_string(
      v.view(start_offset=4, end_offset=v.length() - 1),
    )
    let args = split_css_args(inner)
    let mut max_val : Double? = None
    for arg in args {
      match parse_css_value_to_px(arg, ctx) {
        Some(val) =>
          match max_val {
            None => max_val = Some(val)
            Some(current) => if val > current { max_val = Some(val) }
          }
        None => return None
      }
    }
    return max_val
  }

  // Handle nested clamp()
  if v.has_prefix("clamp(") && v.has_suffix(")") {
    let inner = view_to_string(
      v.view(start_offset=6, end_offset=v.length() - 1),
    )
    let args = split_css_args(inner)
    if args.length() != 3 {
      return None
    }
    let min_px = parse_css_value_to_px(args[0], ctx)
    let val_px = parse_css_value_to_px(args[1], ctx)
    let max_px = parse_css_value_to_px(args[2], ctx)
    match (min_px, val_px, max_px) {
      (Some(min_v), Some(val_v), Some(max_v)) => {
        // clamp(min, val, max) = max(min, min(val, max))
        let clamped = if val_v < min_v {
          min_v
        } else if val_v > max_v {
          max_v
        } else {
          val_v
        }
        return Some(clamped)
      }
      _ => return None
    }
  }

  // Parse simple dimension value
  let dim = resolve_dimension(v.to_string(), ctx)
  match dim {
    @types.Length(px) => Some(px)
    @types.Percent(_) => None // Cannot simplify percentages
    @types.Auto => None
    @types.MinContent => None
    @types.MaxContent => None
    @types.FitContent(_) => None
  }
}

///|
/// Split CSS function arguments by comma (handling nested parentheses)
fn split_css_args(input : String) -> Array[String] {
  let result : Array[String] = []
  let mut current = StringBuilder::new()
  let mut paren_depth = 0
  for i = 0; i < input.length(); i = i + 1 {
    let c = input[i].to_int().unsafe_to_char()
    if c == '(' {
      paren_depth += 1
      current.write_char(c)
    } else if c == ')' {
      paren_depth -= 1
      current.write_char(c)
    } else if c == ',' && paren_depth == 0 {
      let s = current.to_string().trim().to_string()
      if !s.is_empty() {
        result.push(s)
      }
      current = StringBuilder::new()
    } else {
      current.write_char(c)
    }
  }
  let s = current.to_string().trim().to_string()
  if !s.is_empty() {
    result.push(s)
  }
  result
}

///|
/// Parse a simple calc expression (handles + - * / with px values)
fn parse_simple_calc_expr(expr : String, ctx : ComputeContext) -> Double? {
  let mut result : Double = 0.0
  let mut current_op : Char = '+'
  let mut i = 0
  let mut token_start = 0
  while i <= expr.length() {
    let c = if i < expr.length() {
      expr[i].to_int().unsafe_to_char()
    } else {
      ' '
    }
    if c == '+' || c == '-' || c == '*' || c == '/' || i == expr.length() {
      if i > token_start {
        let token = expr.unsafe_substring(start=token_start, end=i).trim()
        if !token.is_empty() {
          match parse_css_value_to_px(token.to_string(), ctx) {
            Some(val) =>
              match current_op {
                '+' => result = result + val
                '-' => result = result - val
                '*' => result = result * val
                '/' => result = result / val
                _ => ()
              }
            None => {
              // Try parsing as unitless number for * and /
              let n = @string.parse_double(token.to_string()) catch {
                _ => return None
              }
              match current_op {
                '*' => result = result * n
                '/' => result = result / n
                '+' | '-' =>
                  // Unitless number for + or - is invalid unless 0
                  if n != 0.0 {
                    return None
                  }
                _ => ()
              }
            }
          }
        }
      }
      if i < expr.length() {
        current_op = c
      }
      token_start = i + 1
    }
    i = i + 1
  }
  Some(result)
}

///|
/// Parse min(), max(), clamp() CSS functions
fn parse_css_math_function(
  expr : String,
  ctx : ComputeContext,
) -> @types.Dimension? {
  let v = expr.trim()

  // Handle min()
  if v.has_prefix("min(") && v.has_suffix(")") {
    match parse_css_value_to_px(v.to_string(), ctx) {
      Some(px) => return Some(@types.Length(px))
      None => return None
    }
  }

  // Handle max()
  if v.has_prefix("max(") && v.has_suffix(")") {
    match parse_css_value_to_px(v.to_string(), ctx) {
      Some(px) => return Some(@types.Length(px))
      None => return None
    }
  }

  // Handle clamp()
  if v.has_prefix("clamp(") && v.has_suffix(")") {
    match parse_css_value_to_px(v.to_string(), ctx) {
      Some(px) => return Some(@types.Length(px))
      None => return None
    }
  }
  None
}

///|
/// Parse calc() terms and return (length_px, percent_ratio)
/// percent_ratio is 0.5 for 50%.
fn parse_calc_terms(expr : String, ctx : ComputeContext) -> (Double, Double)? {
  // Extract the expression inside calc(...)
  let v = expr.trim()
  if !v.has_prefix("calc(") || !v.has_suffix(")") {
    return None
  }
  // Extract content: "calc(" has 5 chars, ")" has 1 char
  let inner = view_to_string(v.view(start_offset=5, end_offset=v.length() - 1))
  // Parse simple expressions: accumulate px and % values
  let mut result_px : Double = 0.0
  let mut result_pct : Double = 0.0
  let mut current_op : Char = '+'
  // Split by operators while preserving them
  let mut i = 0
  let mut token_start = 0
  while i <= inner.length() {
    let c = if i < inner.length() {
      inner[i].to_int().unsafe_to_char()
    } else {
      ' '
    } // End of string
    if c == '+' || c == '-' || c == '*' || c == '/' || i == inner.length() {
      // Process token before operator
      if i > token_start {
        let token = inner.unsafe_substring(start=token_start, end=i).trim()
        if !token.is_empty() {
          // Parse the token
          if token.has_suffix("%") {
            let num_str = view_to_string(
              token.view(end_offset=token.length() - 1),
            )
            let n = @string.parse_double(num_str.to_string()) catch { _ => 0.0 }
            match current_op {
              '+' => result_pct = result_pct + n
              '-' => result_pct = result_pct - n
              '*' => result_pct = result_pct * n
              '/' => result_pct = result_pct / n
              _ => ()
            }
          } else {
            // Try to resolve known length units to px for calc() simplification.
            let resolved_px : Double? = if token.has_suffix("px") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 2),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              Some(n)
            } else if token.has_suffix("vw") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 2),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              Some(n * ctx.viewport_width / 100.0)
            } else if token.has_suffix("vh") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 2),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              Some(n * ctx.viewport_height / 100.0)
            } else if token.has_suffix("vmin") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 4),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              let min_dimension = if ctx.viewport_width < ctx.viewport_height {
                ctx.viewport_width
              } else {
                ctx.viewport_height
              }
              Some(n * min_dimension / 100.0)
            } else if token.has_suffix("vmax") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 4),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              let max_dimension = if ctx.viewport_width > ctx.viewport_height {
                ctx.viewport_width
              } else {
                ctx.viewport_height
              }
              Some(n * max_dimension / 100.0)
            } else if token.has_suffix("rem") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 3),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              Some(n * ctx.root_font_size)
            } else if token.has_suffix("em") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 2),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              Some(n * ctx.font_size)
            } else if token.has_suffix("pt") {
              let num_str = view_to_string(
                token.view(end_offset=token.length() - 2),
              )
              let n = @string.parse_double(num_str.to_string()) catch {
                _ => return None
              }
              Some(n * 96.0 / 72.0)
            } else {
              None
            }
            match resolved_px {
              Some(n) =>
                match current_op {
                  '+' => result_px = result_px + n
                  '-' => result_px = result_px - n
                  '*' => result_px = result_px * n
                  '/' => result_px = result_px / n
                  _ => ()
                }
              None => {
                // Try as unitless number (treat as px if 0, otherwise skip)
                let n = @string.parse_double(token) catch { _ => return None }
                if n == 0.0 {
                  // 0 has no effect on the result
                  ()
                } else {
                  // Non-zero unitless - treat as multiplier for current values if *, /
                  match current_op {
                    '*' => {
                      result_px = result_px * n
                      result_pct = result_pct * n
                    }
                    '/' => {
                      result_px = result_px / n
                      result_pct = result_pct / n
                    }
                    _ => return None
                  }
                }
              }
            }
          }
        }
      }
      // Save operator for next token
      if i < inner.length() {
        current_op = c
      }
      token_start = i + 1
    }
    i = i + 1
  }
  Some((result_px, result_pct / 100.0))
}

///|
/// Extract first percentage token from calc() expression as ratio.
/// Used as a fallback when mixed calc contains multiplicative terms.
fn extract_first_percent_ratio(expr : String) -> Double? {
  let mut i = 0
  while i < expr.length() {
    if expr[i].to_int().unsafe_to_char() == '%' {
      let mut start = i
      while start > 0 {
        let c = expr[start - 1].to_int().unsafe_to_char()
        if (c >= '0' && c <= '9') || c == '.' {
          start = start - 1
          continue
        }
        if c == '+' || c == '-' {
          start = start - 1
        }
        break
      }
      let num = expr.unsafe_substring(start~, end=i).trim()
      if !num.is_empty() {
        let n = @string.parse_double(num.to_string()) catch { _ => return None }
        return Some(n / 100.0)
      }
    }
    i = i + 1
  }
  None
}

///|
/// Parse a calc() expression from a string and try to simplify it
/// Returns Some(dimension) if simplification is possible, None otherwise
fn parse_calc_string(expr : String, ctx : ComputeContext) -> @types.Dimension? {
  match parse_calc_terms(expr, ctx) {
    Some((result_px, result_percent)) =>
      if result_percent.abs() < 0.0001 {
        Some(@types.Length(result_px))
      } else if result_px.abs() < 0.0001 {
        Some(@types.Percent(result_percent))
      } else {
        None
      }
    None => None
  }
}

///|
/// Choose a width reference for mixed calc() fallback.
/// Prefer parent's definite width when available; otherwise use viewport width.
fn mixed_calc_width_reference(ctx : ComputeContext) -> Double {
  match ctx.parent_style {
    Some(parent) =>
      match parent.width {
        @types.Length(w) => w
        @types.Percent(p) => ctx.viewport_width * p
        _ => ctx.viewport_width
      }
    None => ctx.viewport_width
  }
}

///|
/// Fallback for mixed calc() on properties that need concrete dimensions.
/// For expressions like calc(50% - 10px), resolve using a width reference.
fn resolve_dimension_with_calc_percent_fallback(
  value : String,
  ctx : ComputeContext,
) -> @types.Dimension {
  let dim = resolve_dimension(value, ctx)
  match dim {
    @types.Auto =>
      if value.trim().has_prefix("calc(") {
        match parse_calc_terms(value, ctx) {
          Some((length_px, percent_ratio)) =>
            if percent_ratio.abs() >= 0.0001 {
              let percent_for_resolution = if value.contains("*") ||
                value.contains("/") {
                match extract_first_percent_ratio(value) {
                  Some(p) => p
                  None => percent_ratio
                }
              } else {
                percent_ratio
              }
              let basis = mixed_calc_width_reference(ctx)
              @types.Length(basis * percent_for_resolution + length_px)
            } else if length_px.abs() >= 0.0001 {
              @types.Length(length_px)
            } else {
              @types.Length(0.0)
            }
          None => dim
        }
      } else {
        dim
      }
    _ => dim
  }
}

///|
/// Compatibility fallback for mixed calc() in min-size properties.
/// keep current Dimension model and pick axis-specific term when mixed.
fn resolve_dimension_with_mixed_calc_fallback(
  property : String,
  value : String,
  ctx : ComputeContext,
) -> @types.Dimension {
  let dim = resolve_dimension(value, ctx)
  match dim {
    @types.Auto =>
      if value.trim().has_prefix("calc(") {
        match parse_calc_terms(value, ctx) {
          Some((length_px, percent_ratio)) =>
            if length_px.abs() >= 0.0001 && percent_ratio.abs() >= 0.0001 {
              match property {
                "min-width" => @types.Length(length_px)
                "min-height" =>
                  @types.Length(ctx.viewport_width * percent_ratio)
                _ => dim
              }
            } else {
              dim
            }
          None => dim
        }
      } else {
        dim
      }
    _ => dim
  }
}

///|
/// Resolve CSS var() function with context lookup
/// Looks up variable in context, falls back to provided fallback value if not found
fn resolve_var(value : String, ctx : ComputeContext) -> String {
  let v = value.trim().to_string()
  if !v.has_prefix("var(") || !v.has_suffix(")") {
    return v
  }
  // Extract content inside var(...) - skip "var(" (4 chars) and ")" (1 char)
  let inner : String = view_to_string(
    v.view(start_offset=4, end_offset=v.length() - 1),
  )
  // Find the comma separating variable name from fallback
  // Need to handle nested var() and other functions
  let mut paren_depth = 0
  let mut comma_pos = -1
  for i = 0; i < inner.length(); i = i + 1 {
    let c = inner[i].to_int().unsafe_to_char()
    if c == '(' {
      paren_depth += 1
    } else if c == ')' {
      paren_depth -= 1
    } else if c == ',' && paren_depth == 0 {
      comma_pos = i
      break
    }
  }
  // Extract variable name as String
  let var_name : String = if comma_pos > 0 {
    let slice = inner.unsafe_substring(start=0, end=comma_pos)
    slice.trim().to_string()
  } else {
    inner.trim().to_string()
  }
  // Look up variable in context
  match ctx.custom_properties.get(var_name) {
    Some(var_value) =>
      // Variable found - recursively resolve if it contains var()
      if var_value.contains("var(") {
        resolve_var(var_value, ctx)
      } else {
        var_value
      }
    None =>
      // Variable not found - use fallback if available
      if comma_pos > 0 {
        let fallback_slice = inner.unsafe_substring(
          start=comma_pos + 1,
          end=inner.length(),
        )
        let fallback = fallback_slice.trim().to_string()
        // Recursively resolve fallback (it may also contain var())
        resolve_var(fallback, ctx)
      } else {
        // No fallback and no variable - return empty (invalid value)
        ""
      }
  }
}

///|
/// Resolve all var() references in a value string
fn resolve_all_vars(value : String, ctx : ComputeContext) -> String {
  let v = value.trim()
  // If no var() in value, return as-is
  if !v.contains("var(") {
    return v.to_string()
  }
  // Simple case: entire value is a var()
  if v.has_prefix("var(") && v.has_suffix(")") {
    // Check if there's only one var() (no content after)
    let mut depth = 0
    let mut end_pos = 0
    for i = 0; i < v.length(); i = i + 1 {
      let c = v[i].to_int().unsafe_to_char()
      if c == '(' {
        depth += 1
      } else if c == ')' {
        depth -= 1
        if depth == 0 {
          end_pos = i
          break
        }
      }
    }
    if end_pos == v.length() - 1 {
      // Entire value is a single var()
      return resolve_var(v.to_string(), ctx)
    }
  }
  // Complex case: var() embedded in value like "10px var(--gap) 20px"
  // Need to replace each var() with its resolved value
  let result = StringBuilder::new()
  let mut i = 0
  while i < v.length() {
    // Look for "var("
    if i + 4 <= v.length() {
      let substr = view_to_string(v.view(start_offset=i, end_offset=i + 4))
      if substr == "var(" {
        // Find matching closing paren
        let mut depth = 1
        let mut j = i + 4
        while j < v.length() && depth > 0 {
          let c = v[j].to_int().unsafe_to_char()
          if c == '(' {
            depth += 1
          } else if c == ')' {
            depth -= 1
          }
          j += 1
        }
        // Extract and resolve the var()
        let var_expr = view_to_string(v.view(start_offset=i, end_offset=j))
        let resolved = resolve_var(var_expr, ctx)
        result.write_string(resolved)
        i = j
        continue
      }
    }
    result.write_char(v[i].to_int().unsafe_to_char())
    i += 1
  }
  result.to_string()
}

///|
fn strip_css_ascii_whitespace(value : String) -> String {
  let sb = StringBuilder::new()
  for c in value.iter() {
    if c != ' ' && c != '\t' && c != '\n' && c != '\r' && c != '\u000C' {
      sb.write_char(c)
    }
  }
  sb.to_string()
}

///|
/// Resolve dimension with relative unit conversion
fn resolve_dimension(value : String, ctx : ComputeContext) -> @types.Dimension {
  // First resolve any var() functions
  let raw_v = resolve_all_vars(value, ctx).trim()
  let v = strip_css_ascii_whitespace(raw_v.to_string())
  if v == "auto" || v == "none" {
    return @types.Dimension::Auto
  }

  // Handle intrinsic sizing keywords
  if v == "min-content" {
    return @types.Dimension::MinContent
  }
  if v == "max-content" {
    return @types.Dimension::MaxContent
  }
  if v == "fit-content" {
    // fit-content without argument is equivalent to fit-content(max-content)
    return @types.Dimension::FitContent(1.0e10)
  }

  // Handle calc() expressions
  if raw_v.has_prefix("calc(") {
    match parse_calc_string(raw_v.to_string(), ctx) {
      Some(dim) => return dim
      None => return @types.Dimension::Auto // Fallback for complex calc
    }
  }

  // Handle min(), max(), clamp() CSS math functions
  if raw_v.has_prefix("min(") ||
    raw_v.has_prefix("max(") ||
    raw_v.has_prefix("clamp(") {
    match parse_css_math_function(raw_v.to_string(), ctx) {
      Some(dim) => return dim
      None => return @types.Dimension::Auto // Fallback for complex expressions
    }
  }

  // Handle rem units (check before em)
  match v.strip_suffix("rem") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * ctx.root_font_size)
    }
    None => ()
  }

  // Handle em units
  match v.strip_suffix("em") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * ctx.font_size)
    }
    None => ()
  }

  // Handle vw units
  match v.strip_suffix("vw") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * ctx.viewport_width / 100.0)
    }
    None => ()
  }

  // Handle vh units
  match v.strip_suffix("vh") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * ctx.viewport_height / 100.0)
    }
    None => ()
  }

  // Handle vmin units (smaller of vw or vh)
  match v.strip_suffix("vmin") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      let min_dimension = if ctx.viewport_width < ctx.viewport_height {
        ctx.viewport_width
      } else {
        ctx.viewport_height
      }
      return @types.Dimension::Length(n * min_dimension / 100.0)
    }
    None => ()
  }

  // Handle vmax units (larger of vw or vh)
  match v.strip_suffix("vmax") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      let max_dimension = if ctx.viewport_width > ctx.viewport_height {
        ctx.viewport_width
      } else {
        ctx.viewport_height
      }
      return @types.Dimension::Length(n * max_dimension / 100.0)
    }
    None => ()
  }

  // Handle ch units. Ahem is square (1ch ~= 1em); our default fallback keeps
  // the historical 0.5em approximation for generic monospace rendering.
  match v.strip_suffix("ch") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * ctx.font_size * ch_unit_ratio(ctx))
    }
    None => ()
  }

  // Handle ex units (x-height, approximated as 0.5em)
  match v.strip_suffix("ex") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * ctx.font_size * 0.5)
    }
    None => ()
  }

  // Handle pt units (1pt = 1/72 inch = 96/72 px ≈ 1.333px)
  match v.strip_suffix("pt") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * 96.0 / 72.0)
    }
    None => ()
  }

  // Handle pc units (1pc = 12pt = 16px)
  match v.strip_suffix("pc") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * 16.0)
    }
    None => ()
  }

  // Handle in units (1in = 96px)
  match v.strip_suffix("in") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * 96.0)
    }
    None => ()
  }

  // Handle cm units (1cm = 96/2.54 px ≈ 37.795px)
  match v.strip_suffix("cm") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * 96.0 / 2.54)
    }
    None => ()
  }

  // Handle mm units (1mm = 96/25.4 px ≈ 3.7795px)
  match v.strip_suffix("mm") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * 96.0 / 25.4)
    }
    None => ()
  }

  // Handle Q units (1Q = 1/4mm = 96/101.6 px)
  match v.strip_suffix("Q") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return @types.Dimension::Auto
      }
      return @types.Dimension::Length(n * 96.0 / 101.6)
    }
    None => ()
  }

  // Fall back to simple dimension parsing (px, %)
  parse_dimension(v.to_string())
}

///|
/// Check if a string is a pure number (no units)
fn is_pure_number(s : String) -> Bool {
  let v = s.trim()
  if v.is_empty() {
    return false
  }
  // Try to parse as double - will fail if it has units
  let _ = @string.parse_double(v.to_string()) catch { _ => return false }
  // Make sure it doesn't have any unit suffixes that parse_double might accept
  for i = 0; i < v.length(); i = i + 1 {
    let c = v[i]
    if !(c == '0' ||
      c == '1' ||
      c == '2' ||
      c == '3' ||
      c == '4' ||
      c == '5' ||
      c == '6' ||
      c == '7' ||
      c == '8' ||
      c == '9' ||
      c == '.' ||
      c == '-' ||
      c == '+') {
      return false
    }
  }
  true
}

///|
/// 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
  // 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])
        }
      }
      _ => () // Ignore unsupported functions like matrix/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,
  }
}

///|
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
  }
  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 @style.TranslateValue::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 @style.TranslateValue::Length(0.0)
    }
    @style.TranslateValue::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 @style.TranslateValue::Length(0.0)
    }
    @style.TranslateValue::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 @style.TranslateValue::Length(0.0)
    }
    @style.TranslateValue::Length(n * 16.0)
  } else {
    // Try parsing as plain number (assumed px)
    let n = @string.parse_double(v) catch {
      _ => return @style.TranslateValue::Length(0.0)
    }
    @style.TranslateValue::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("vw") ||
  v.has_suffix("vh")
}

///|
/// Apply inheritance for inherited properties not in cascaded values
fn apply_inheritance(
  builder : StyleBuilder,
  cascaded : @cascade.CascadedValues,
  ctx : ComputeContext,
) -> Unit {
  // List of inherited properties we support
  let inherited_props = [
    "direction", "writing-mode", "text-align", "line-height", "font-size", "font-family",
    "font-weight", "font-style", "color", "visibility", "white-space", "pointer-events",
  ]
  for prop in inherited_props {
    let covered_by_font_shorthand = cascaded.has("font") &&
      (
        prop == "font-size" ||
        prop == "line-height" ||
        prop == "font-family" ||
        prop == "font-weight"
      )
    // If property not in cascaded values, inherit from parent
    if !cascaded.has(prop) && !covered_by_font_shorthand {
      match ctx.parent_style {
        Some(parent) => {
          let value = get_style_value_as_string(prop, parent)
          apply_property(builder, prop, value, ctx)
        }
        None => ()
      }
    }
  }
}

///|
/// Compute style from inline style string (raw, without box-sizing adjustment)
/// Use this when merging individual properties into an existing style.
/// The caller is responsible for calling adjust_for_box_sizing on the final result.
pub fn compute_inline_raw(
  inline_css : String,
  ctx : ComputeContext,
) -> @style.Style {
  // Parse inline style to declarations
  let decls : Array[@cascade.Declaration] = []
  // Simple parsing: split by semicolon
  let pairs = inline_css.split(";")
  let mut order = 0
  for pair in pairs {
    let pair_str = pair.to_string().trim()
    if pair_str.is_empty() {
      continue
    }
    // Find colon
    let mut colon_pos = -1
    for i = 0; i < pair_str.length(); i = i + 1 {
      if pair_str[i].to_int().unsafe_to_char() == ':' {
        colon_pos = i
        break
      }
    }
    if colon_pos > 0 {
      let prop = view_to_string(pair_str.view(end_offset=colon_pos)).trim()
      let val = view_to_string(pair_str.view(start_offset=colon_pos + 1)).trim()
      if !prop.is_empty() && !val.is_empty() {
        decls.push(
          @cascade.Declaration::with_metadata(
            prop.to_string(),
            @cascade.PropertyValue::Value(val.to_string()),
            @cascade.Origin::Author,
            @cascade.Importance::Normal,
            { a: 1000, b: 0, c: 0 },
            order,
          ),
        )
        order += 1
      }
    }
  }

  // Cascade
  let cascaded = @cascade.cascade(decls)

  // Compute (without box-sizing adjustment)
  compute(cascaded, ctx)
}

///|
/// Compute style from inline style string
/// This is the standard function that applies box-sizing adjustment.
pub fn compute_inline(
  inline_css : String,
  ctx : ComputeContext,
) -> @style.Style {
  let style = compute_inline_raw(inline_css, ctx)
  // Convert content-box dimensions to border-box dimensions
  // The layout engine always works with outer (border-box) dimensions
  adjust_for_box_sizing(style)
}

///|
/// Apply a single CSS property to an existing style directly
/// This skips the CSS string parsing step for better performance
pub fn apply_property_direct(
  style : @style.Style,
  property : String,
  value : String,
  ctx : ComputeContext,
) -> @style.Style {
  // Special handling for font-size: when font-size changes,
  // update line-height proportionally to maintain the ratio
  if property == "font-size" {
    let builder = StyleBuilder::from_style(style)
    let old_fs = style.font_size
    apply_property(builder, property, value, ctx)
    let new_fs = builder.font_size
    // Maintain line-height / font-size ratio
    let lh_ratio = if old_fs > 0.0 { style.line_height / old_fs } else { 1.0 }
    builder.line_height = new_fs * lh_ratio
    return builder.build()
  }
  let builder = StyleBuilder::from_style(style)
  apply_property(builder, property, value, ctx)
  builder.build()
}

///|
/// Adjust dimensions for box-sizing: content-box
/// When box-sizing is content-box (default), specified width/height are content dimensions.
/// The layout engine expects outer dimensions (border-box), so we adjust here.
fn adjust_for_box_sizing(style : @style.Style) -> @style.Style {
  match style.box_sizing {
    @types.BorderBox => style // No adjustment needed
    @types.ContentBox => {
      // Calculate padding and border sums
      let padding_h = resolve_dimension_to_px(style.padding.left) +
        resolve_dimension_to_px(style.padding.right)
      let padding_v = resolve_dimension_to_px(style.padding.top) +
        resolve_dimension_to_px(style.padding.bottom)
      let border_h = resolve_dimension_to_px(style.border.left) +
        resolve_dimension_to_px(style.border.right)
      let border_v = resolve_dimension_to_px(style.border.top) +
        resolve_dimension_to_px(style.border.bottom)

      // Adjust width and height to include padding+border
      let adjusted_width = match style.width {
        @types.Dimension::Length(w) =>
          @types.Dimension::Length(w + padding_h + border_h)
        other => other
      }
      let adjusted_height = match style.height {
        @types.Dimension::Length(h) =>
          @types.Dimension::Length(h + padding_v + border_v)
        other => other
      }
      // Adjust min/max constraints too
      let adjusted_min_width = match style.min_width {
        @types.Dimension::Length(w) =>
          @types.Dimension::Length(w + padding_h + border_h)
        other => other
      }
      let adjusted_min_height = match style.min_height {
        @types.Dimension::Length(h) =>
          @types.Dimension::Length(h + padding_v + border_v)
        other => other
      }
      let adjusted_max_width = match style.max_width {
        @types.Dimension::Length(w) =>
          @types.Dimension::Length(w + padding_h + border_h)
        other => other
      }
      let adjusted_max_height = match style.max_height {
        @types.Dimension::Length(h) =>
          @types.Dimension::Length(h + padding_v + border_v)
        other => other
      }
      {
        ..style,
        width: adjusted_width,
        height: adjusted_height,
        min_width: adjusted_min_width,
        min_height: adjusted_min_height,
        max_width: adjusted_max_width,
        max_height: adjusted_max_height,
        // Mark as adjusted (now using border-box semantics internally)
        box_sizing: @types.BorderBox,
      }
    }
  }
}

///|
/// Helper to resolve a dimension to pixels (for padding/border calculation)
fn resolve_dimension_to_px(dim : @types.Dimension) -> Double {
  match dim {
    @types.Length(v) => v
    @types.Percent(_) => 0.0 // Percentages are resolved later
    @types.Auto => 0.0
    @types.MinContent => 0.0 // Intrinsic sizing resolved during layout
    @types.MaxContent => 0.0
    @types.FitContent(_) => 0.0
  }
}

///|
/// Parse font-size value and return computed value in pixels
fn parse_font_size(value : String, ctx : ComputeContext) -> Double {
  let v = value.trim()

  // Handle rem units (relative to root font-size) first.
  // "1rem" also ends with "em", so ordering matters.
  match v.strip_suffix("rem") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch { _ => 1.0 }
      return n * ctx.root_font_size
    }
    None => ()
  }

  // Handle em units (relative to parent font-size)
  match v.strip_suffix("em") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch { _ => 1.0 }
      return n * ctx.font_size
    }
    None => ()
  }

  // Handle px units
  match v.strip_suffix("px") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return ctx.font_size
      }
      return n
    }
    None => ()
  }

  // Handle pt units (1pt = 1.333px)
  match v.strip_suffix("pt") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return ctx.font_size
      }
      return n * 1.333
    }
    None => ()
  }

  // Handle percent (relative to parent font-size)
  match v.strip_suffix("%") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return ctx.font_size
      }
      return n / 100.0 * ctx.font_size
    }
    None => ()
  }

  let plain_number = @string.parse_double(v) catch { _ => -1.0 }
  if plain_number == 0.0 {
    return 0.0
  }

  // Handle keyword values
  match v {
    "xx-small" => 9.0
    "x-small" => 10.0
    "small" => 13.0
    "medium" => 16.0
    "large" => 18.0
    "x-large" => 24.0
    "xx-large" => 32.0
    "smaller" => ctx.font_size * 0.833
    "larger" => ctx.font_size * 1.2
    _ => ctx.font_size // Default to inherited font-size
  }
}

///|
/// Parse line-height value and return computed value in pixels
fn parse_line_height(value : String, font_size : Double) -> Double {
  let v = value.trim()

  // Handle unitless number (multiplier of font-size)
  // Check if it's a pure number
  let is_pure_number = {
    let mut pure = true
    for i = 0; i < v.length(); i = i + 1 {
      let c = v[i].to_int().unsafe_to_char()
      if c != '.' && !(c >= '0' && c <= '9') {
        pure = false
        break
      }
    }
    pure
  }
  if is_pure_number && !v.is_empty() {
    let n = @string.parse_double(v.to_string()) catch { _ => 1.0 }
    return n * font_size
  }

  // Handle px units
  match v.strip_suffix("px") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return font_size
      }
      return n
    }
    None => ()
  }

  // Handle em units
  match v.strip_suffix("em") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return font_size
      }
      return n * font_size
    }
    None => ()
  }

  // Handle percent (relative to font-size)
  match v.strip_suffix("%") {
    Some(num_str) => {
      let n = @string.parse_double(num_str.to_string()) catch {
        _ => return font_size
      }
      return n / 100.0 * font_size
    }
    None => ()
  }

  // "normal" = 1.2 * font-size typically
  if v == "normal" {
    return font_size * 1.2
  }

  // Default to font-size
  font_size
}

///|
priv struct ParsedFontShorthand {
  font_size : Double
  line_height : Double
  font_weight : Double
  font_family : String
}

///|
/// Parse font shorthand and extract font-size and line-height
/// Supports: [style] [variant] [weight] size[/line-height] family
fn parse_font_shorthand(
  value : String,
  ctx : ComputeContext,
) -> ParsedFontShorthand {
  let v = value.trim()

  // Split by whitespace
  let parts : Array[String] = []
  let mut current = StringBuilder::new()
  let mut in_quotes = false
  for c in v.iter() {
    if c == '"' || c == '\'' {
      in_quotes = !in_quotes
      current.write_char(c)
    } else if (c == ' ' || c == '\t') && !in_quotes {
      let s = current.to_string()
      if s.length() > 0 {
        parts.push(s)
      }
      current = StringBuilder::new()
    } else {
      current.write_char(c)
    }
  }
  let s = current.to_string()
  if s.length() > 0 {
    parts.push(s)
  }

  // Find the part with font-size (and optional /line-height)
  // Font-size is required and comes before font-family
  // Look for a part that starts with a digit or contains 'px', 'em', 'rem', '%'
  let mut font_size = ctx.font_size
  let mut line_height = ctx.font_size
  let mut font_weight = 400.0
  let mut size_index = -1
  for idx = 0; idx < parts.length(); idx = idx + 1 {
    let part = parts[idx]
    // Check if this part contains size/line-height
    if part.contains("/") {
      // Split by /
      let mut slash_idx = -1
      for i = 0; i < part.length(); i = i + 1 {
        if part[i] == '/' {
          slash_idx = i
          break
        }
      }
      if slash_idx > 0 {
        let size_part = part.unsafe_substring(start=0, end=slash_idx)
        let lh_part = part.unsafe_substring(
          start=slash_idx + 1,
          end=part.length(),
        )
        font_size = parse_font_size(size_part, ctx)
        line_height = parse_line_height(lh_part, font_size)
        size_index = idx
        break
      }
    } else {
      // Check if it's a size value (starts with digit or contains unit)
      let first_char = if part.length() > 0 { part[0] } else { ' ' }
      if first_char >= '0' && first_char <= '9' {
        font_size = parse_font_size(part, ctx)
        line_height = font_size // Default line-height = font-size (ratio 1)
        size_index = idx
        break
      }
    }
  }
  if size_index > 0 {
    for idx = 0; idx < size_index; idx = idx + 1 {
      let part = parts[idx].trim().to_lower().to_string()
      if part == "normal" ||
        part == "bold" ||
        part == "bolder" ||
        part == "lighter" {
        font_weight = parse_font_weight(part)
        continue
      }
      let parsed_weight = @string.parse_double(part) catch { _ => -1.0 }
      if parsed_weight >= 1.0 && parsed_weight <= 1000.0 {
        font_weight = parsed_weight
      }
    }
  }
  let font_family = if size_index >= 0 && size_index + 1 < parts.length() {
    let family_parts : Array[String] = []
    for idx = size_index + 1; idx < parts.length(); idx = idx + 1 {
      family_parts.push(parts[idx])
    }
    parse_font_family(family_parts.join(" "))
  } else {
    ""
  }
  { font_size, line_height, font_weight, font_family }
}

// =============================================================================
// Multi-column / Fragmentation Parsing
// =============================================================================

///|
fn parse_column_count(value : String) -> Int? {
  let trimmed = value.trim().to_string()
  if trimmed.is_empty() || trimmed == "auto" {
    return None
  }
  try {
    let parsed = @string.parse_int(trimmed)
    if parsed >= 1 {
      Some(parsed)
    } else {
      None
    }
  } catch {
    _ => None
  }
}

///|
fn parse_column_width(value : String, ctx : ComputeContext) -> @types.Dimension {
  let trimmed = value.trim().to_string()
  if trimmed.is_empty() || trimmed == "auto" {
    return @types.Dimension::Auto
  }
  let dim = resolve_dimension_with_calc_percent_fallback(trimmed, ctx)
  match dim {
    @types.Dimension::Length(v) =>
      if v > 0.0 {
        @types.Dimension::Length(v)
      } else {
        @types.Dimension::Auto
      }
    @types.Dimension::Percent(v) =>
      if v > 0.0 {
        @types.Dimension::Percent(v)
      } else {
        @types.Dimension::Auto
      }
    @types.Dimension::Auto
    | @types.Dimension::MinContent
    | @types.Dimension::MaxContent
    | @types.Dimension::FitContent(_) => @types.Dimension::Auto
  }
}

///|
fn parse_column_fill(value : String) -> @style.ColumnFill {
  match value.trim().to_string() {
    "auto" => @style.ColumnFill::Auto
    _ => @style.ColumnFill::Balance
  }
}

///|
fn parse_break_before(value : String) -> @style.BreakBefore {
  match value.trim().to_string() {
    "column" => @style.BreakBefore::Column
    "avoid" => @style.BreakBefore::Avoid
    _ => @style.BreakBefore::Auto
  }
}

///|
fn parse_break_inside(value : String) -> @style.BreakInside {
  match value.trim().to_string() {
    "avoid" => @style.BreakInside::Avoid
    _ => @style.BreakInside::Auto
  }
}

///|
fn parse_column_span(value : String) -> @style.ColumnSpan {
  match value.trim().to_string() {
    "all" => @style.ColumnSpan::All
    _ => @style.ColumnSpan::None
  }
}

///|
fn parse_columns_shorthand(
  value : String,
  ctx : ComputeContext,
) -> (Int?, @types.Dimension)? {
  let parts : Array[StringView] = value
    .trim()
    .split(" ")
    .filter(fn(s) { !s.is_empty() })
    .collect()
  if parts.length() == 0 {
    return None
  }
  let mut count : Int? = None
  let mut width = @types.Dimension::Auto
  for part in parts {
    let token = part.to_string()
    if token == "auto" {
      continue
    }
    if count is None {
      match parse_column_count(token) {
        Some(v) => {
          count = Some(v)
          continue
        }
        None => ()
      }
    }
    let parsed_width = parse_column_width(token, ctx)
    match parsed_width {
      @types.Dimension::Auto => ()
      _ => width = parsed_width
    }
  }
  Some((count, width))
}

// =============================================================================
// Containment Parsing
// =============================================================================

///|
/// Parse contain-intrinsic-inline-size / contain-intrinsic-block-size.
/// Returns a content-box fallback length in px, or None for none/auto.
fn parse_contain_intrinsic_axis(
  value : String,
  ctx : ComputeContext,
) -> Double? {
  let v = resolve_all_vars(value, ctx).trim().to_string()
  if v.is_empty() || v == "none" || v == "auto" {
    return None
  }
  match resolve_dimension(v, ctx) {
    @types.Length(length) => if length > 0.0 { Some(length) } else { Some(0.0) }
    _ => None
  }
}

///|
/// Parse contain-intrinsic-size shorthand.
/// One value applies to both inline/block axes; two values map to inline then block.
fn parse_contain_intrinsic_size(
  value : String,
  ctx : ComputeContext,
) -> (Double?, Double?)? {
  let v = resolve_all_vars(value, ctx).trim().to_string()
  if v.is_empty() {
    return None
  }
  let axis_values : Array[Double?] = []
  let parts = v.split(" ")
  for part in parts {
    let p = part.to_string().trim().to_string()
    if p.is_empty() || p == "auto" {
      continue
    }
    if p == "none" {
      axis_values.push(None)
      continue
    }
    match parse_contain_intrinsic_axis(p, ctx) {
      Some(length) => axis_values.push(Some(length))
      None => ()
    }
  }
  if axis_values.length() == 0 {
    None
  } else if axis_values.length() == 1 {
    Some((axis_values[0], axis_values[0]))
  } else {
    Some((axis_values[0], axis_values[1]))
  }
}

///|
/// Parse contain property
fn parse_contain(value : String) -> @style.Contain {
  let v = value.trim().to_string()
  // Handle keywords
  if v == "none" {
    return @style.Contain::none()
  }
  if v == "strict" {
    return @style.Contain::strict()
  }
  if v == "content" {
    return @style.Contain::content()
  }
  // Parse space-separated values
  let mut result = @style.Contain::none()
  let parts = v.split(" ")
  for part in parts {
    let p = part.to_string().trim()
    if p == "size" {
      result = { ..result, size: true }
    } else if p == "inline-size" {
      result = { ..result, inline_size: true }
    } else if p == "layout" {
      result = { ..result, layout: true }
    } else if p == "paint" {
      result = { ..result, paint: true }
    } else if p == "style" {
      result = { ..result, style: true }
    }
  }
  result
}

///|
fn merge_contain(
  base : @style.Contain,
  extra : @style.Contain,
) -> @style.Contain {
  let merged : @style.Contain = {
    size: base.size || extra.size,
    inline_size: base.inline_size || extra.inline_size,
    layout: base.layout || extra.layout,
    paint: base.paint || extra.paint,
    style: base.style || extra.style,
  }
  if merged.size {
    { ..merged, inline_size: false }
  } else {
    merged
  }
}

///|
/// Parse container-type into implied contain bits.
/// Supported: normal | size | inline-size
fn parse_container_type(value : String) -> @style.Contain? {
  match value.trim().to_string() {
    "normal" => Some(@style.Contain::none())
    "size" => Some({ ..@style.Contain::none(), size: true, style: true })
    "inline-size" =>
      Some({ ..@style.Contain::none(), inline_size: true, style: true })
    _ => None
  }
}

// Paint Property Parsing
// =============================================================================

///|
/// Parse visibility property
fn parse_visibility(value : String) -> @style.Visibility {
  match value.trim().to_string() {
    "visible" => @style.Visibility::Visible
    "hidden" => @style.Visibility::Hidden
    "collapse" => @style.Visibility::Collapse
    _ => @style.Visibility::Visible
  }
}

///|
fn parse_pointer_events(value : String) -> @style.PointerEvents {
  match value.trim().to_string() {
    "none" => @style.PointerEvents::None
    _ => @style.PointerEvents::Auto
  }
}

///|
/// Parse vertical-align property
fn parse_vertical_align(value : String) -> @style.VerticalAlign {
  match value.trim().to_string() {
    "baseline" => @style.VerticalAlign::Baseline
    "top" => @style.VerticalAlign::Top
    "middle" => @style.VerticalAlign::Middle
    "bottom" => @style.VerticalAlign::Bottom
    "text-top" => @style.VerticalAlign::TextTop
    "text-bottom" => @style.VerticalAlign::TextBottom
    "sub" => @style.VerticalAlign::Sub
    "super" => @style.VerticalAlign::Super
    _ => @style.VerticalAlign::Baseline
  }
}

///|
/// Parse z-index property
fn parse_z_index(value : String) -> @style.ZIndex {
  let trimmed = value.trim().to_string()
  if trimmed == "auto" {
    @style.ZIndex::Auto
  } else {
    try {
      let v = @string.parse_int(trimmed)
      @style.ZIndex::Value(v)
    } catch {
      _ => @style.ZIndex::Auto
    }
  }
}

///|
/// Parse opacity property (0.0 to 1.0)
fn parse_opacity(value : String) -> Double {
  let trimmed = value.trim().to_string()
  try {
    let v = @string.parse_double(trimmed)
    // Clamp to valid range
    if v < 0.0 {
      0.0
    } else if v > 1.0 {
      1.0
    } else {
      v
    }
  } catch {
    _ => 1.0
  }
}