///|
/// Replaced element sizing and form-control helpers.

///|
/// Check if element is a replaced element (img, input, video, etc.)
/// Replaced elements have intrinsic dimensions and don't contain text
fn is_replaced_element(tag : String) -> Bool {
  match tag.to_lower() {
    "img"
    | "input"
    | "textarea"
    | "select"
    | "meter"
    | "progress"
    | "video"
    | "audio"
    | "canvas"
    | "svg"
    | "iframe"
    | "object"
    | "embed" => true
    _ => false
  }
}

///|
/// Create a MeasureFunc for image with intrinsic dimensions
fn create_image_measure(
  intrinsic_width : Double,
  intrinsic_height : Double,
) -> @layout_types.MeasureFunc {
  {
    func: fn(
      _available_width : Double,
      _available_height : Double,
    ) -> @layout_types.IntrinsicSize {
      // If intrinsic size is specified, use it
      // Otherwise default to 0
      let width = if intrinsic_width > 0.0 { intrinsic_width } else { 0.0 }
      let height = if intrinsic_height > 0.0 { intrinsic_height } else { 0.0 }

      // For images with intrinsic dimensions, min and max are the same
      {
        min_width: width,
        max_width: width,
        min_height: height,
        max_height: height,
      }
    },
  }
}

///|
fn broken_img_alt_uses_non_replaced_overflow_box(style : @style.Style) -> Bool {
  style.overflow_x != @types.Visible || style.overflow_y != @types.Visible
}

///|
/// Create a MeasureFunc for input elements with intrinsic dimensions
fn create_input_measure(
  intrinsic_width : Double,
  intrinsic_height : Double,
) -> @layout_types.MeasureFunc {
  {
    func: fn(
      _available_width : Double,
      _available_height : Double,
    ) -> @layout_types.IntrinsicSize {
      // Input elements have fixed intrinsic dimensions based on type
      {
        min_width: intrinsic_width,
        max_width: intrinsic_width,
        min_height: intrinsic_height,
        max_height: intrinsic_height,
      }
    },
  }
}

///|
fn is_text_like_input_type(input_type : String) -> Bool {
  match input_type {
    "text" | "password" | "search" | "email" | "url" | "tel" | "number" => true
    _ => false
  }
}

///|
fn mask_password_value(value : String) -> String {
  let masked = StringBuilder::new()
  for _ in value {
    masked.write_char('*')
  }
  masked.to_string()
}

///|
fn resolve_input_paint_text(
  input_type : String,
  attributes : Map[String, String],
) -> String? {
  match input_type {
    "button" =>
      match attributes.get("value") {
        Some(value) if !value.is_empty() => return Some(value)
        _ => return None
      }
    "submit" =>
      match attributes.get("value") {
        Some(value) if !value.is_empty() => return Some(value)
        _ => return Some("Submit")
      }
    "reset" =>
      match attributes.get("value") {
        Some(value) if !value.is_empty() => return Some(value)
        _ => return Some("Reset")
      }
    _ => ()
  }
  if !is_text_like_input_type(input_type) {
    return None
  }
  match attributes.get("value") {
    Some(value) if !value.is_empty() =>
      if input_type == "password" {
        Some(mask_password_value(value))
      } else {
        Some(value)
      }
    _ =>
      match attributes.get("placeholder") {
        Some(placeholder) if !placeholder.is_empty() => Some(placeholder)
        _ => None
      }
  }
}

///|
fn input_uses_placeholder_text(
  input_type : String,
  attributes : Map[String, String],
) -> Bool {
  if !is_text_like_input_type(input_type) {
    return false
  }
  match attributes.get("value") {
    Some(value) if !value.is_empty() => false
    _ =>
      match attributes.get("placeholder") {
        Some(placeholder) => !placeholder.is_empty()
        None => false
      }
  }
}

///|
fn inset_is_definite(dim : @types.Dimension) -> Bool {
  match dim {
    @types.Length(_) | @types.Percent(_) => true
    _ => false
  }
}

///|
fn should_preserve_auto_replaced_width(style : @style.Style) -> Bool {
  if style.width != @types.Auto {
    return false
  }
  match style.position {
    @types.Absolute | @types.Fixed =>
      inset_is_definite(style.inset.left) &&
      inset_is_definite(style.inset.right)
    _ => false
  }
}

///|
fn should_preserve_auto_replaced_height(style : @style.Style) -> Bool {
  if style.height != @types.Auto {
    return false
  }
  match style.position {
    @types.Absolute | @types.Fixed =>
      inset_is_definite(style.inset.top) &&
      inset_is_definite(style.inset.bottom)
    _ => false
  }
}

///|
fn is_definite_replaced_size(dim : @types.Dimension) -> Bool {
  match dim {
    @types.Length(_) => true
    _ => false
  }
}

///|
fn should_apply_intrinsic_replaced_aspect_ratio(
  width : @types.Dimension,
  height : @types.Dimension,
) -> Bool {
  !(is_definite_replaced_size(width) && is_definite_replaced_size(height))
}

///|
/// Estimate a control character advance from the active text metrics provider.
/// Falls back to the renderer's historical 0.5em approximation.
fn resolve_control_char_width(
  font_size : Double,
  line_height : Double,
  writing_mode : @style.WritingMode,
) -> Double {
  let zero_measure = create_text_measure_with_provider(
    "0",
    font_size,
    line_height,
    @style.WhiteSpace::Nowrap,
    writing_mode,
  )
  let intrinsic = (zero_measure.func)(9999.0, 9999.0)
  let measured = if intrinsic.max_width > 0.0 {
    intrinsic.max_width
  } else if intrinsic.min_width > 0.0 {
    intrinsic.min_width
  } else {
    0.0
  }
  if measured > 0.0 {
    measured
  } else {
    font_size * 0.5
  }
}

///|
fn resolve_control_label_width(
  label : String,
  font_size : Double,
  line_height : Double,
  writing_mode : @style.WritingMode,
) -> Double {
  if label.is_empty() {
    return 0.0
  }
  let label_measure = create_text_measure_with_provider(
    label,
    font_size,
    line_height,
    @style.WhiteSpace::Nowrap,
    writing_mode,
  )
  let intrinsic = (label_measure.func)(9999.0, 9999.0)
  let char_width = resolve_control_char_width(
    font_size, line_height, writing_mode,
  )
  let mut label_cols = 0
  let mut has_non_ascii = false
  for c in label.iter() {
    if c.to_int() > 127 {
      has_non_ascii = true
    }
    label_cols = label_cols + char_display_width(c)
  }
  let fallback_width = label_cols.to_double() * char_width
  let measured_width = if intrinsic.max_width > 0.0 {
    intrinsic.max_width
  } else if intrinsic.min_width > 0.0 {
    intrinsic.min_width
  } else {
    fallback_width
  }
  if has_non_ascii && measured_width < fallback_width {
    fallback_width
  } else {
    measured_width
  }
}

///|
/// Create a MeasureFunc for br elements
/// In horizontal writing-mode, br has 0 width and line-height height.
/// In vertical writing-mode, br advances one line on x-axis.
fn create_br_measure(
  line_height : Double,
  font_size : Double,
  writing_mode : @style.WritingMode,
) -> @layout_types.MeasureFunc {
  let is_vertical = writing_mode.is_vertical()
  let leading = if line_height > font_size {
    line_height - font_size
  } else {
    0.0
  }
  let base_advance = if font_size > 0.0 {
    font_size + leading * 0.1
  } else {
    line_height
  }
  let advance = if base_advance > 0.0 { base_advance } else { 0.0 }
  {
    func: fn(
      _available_width : Double,
      _available_height : Double,
    ) -> @layout_types.IntrinsicSize {
      if is_vertical {
        // Vertical flow: br occupies one column advance.
        {
          min_width: advance,
          max_width: advance,
          min_height: 0.0,
          max_height: 0.0,
        }
      } else {
        // Horizontal flow: br occupies one line advance.
        {
          min_width: 0.0,
          max_width: 0.0,
          min_height: advance,
          max_height: advance,
        }
      }
    },
  }
}