///|
/// Intrinsic media size extraction helpers.

///|
/// Parse dimension from HTML attribute (e.g., "300" or "300px")
fn parse_html_dimension(value : String) -> Double? {
  let trimmed = value.trim().to_owned()
  if trimmed.is_empty() {
    return None
  }
  // Remove "px" suffix if present
  let num_str = if trimmed.has_suffix("px") {
    trimmed.unsafe_substring(start=0, end=trimmed.length() - 2)
  } else {
    trimmed
  }
  Some(
    // Parse as double
    @string.parse_double(num_str),
  ) catch {
    _ => None
  }
}

///|
/// Simple URL decode for common SVG data URI characters
fn url_decode(s : String) -> String {
  let result = StringBuilder::new()
  let mut i = 0
  while i < s.length() {
    let c = s[i].to_int().unsafe_to_char()
    if c == '%' && i + 2 < s.length() {
      // Parse hex value
      let hex1 = s[i + 1].to_int().unsafe_to_char()
      let hex2 = s[i + 2].to_int().unsafe_to_char()
      fn hex_digit(c : Char) -> Int {
        if c >= '0' && c <= '9' {
          c.to_int() - '0'.to_int()
        } else if c >= 'a' && c <= 'f' {
          c.to_int() - 'a'.to_int() + 10
        } else if c >= 'A' && c <= 'F' {
          c.to_int() - 'A'.to_int() + 10
        } else {
          0
        }
      }

      let value = hex_digit(hex1) * 16 + hex_digit(hex2)
      result.write_char(value.unsafe_to_char())
      i = i + 3
    } else {
      result.write_char(c)
      i = i + 1
    }
  }
  result.to_string()
}

///|
/// Find index of pattern in string, returns -1 if not found
fn find_string_index(s : String, pattern : String) -> Int {
  if pattern.length() == 0 || pattern.length() > s.length() {
    return -1
  }
  for i = 0; i <= s.length() - pattern.length(); i = i + 1 {
    let mut found = true
    for j = 0; j < pattern.length(); j = j + 1 {
      if s[i + j].to_int() != pattern[j].to_int() {
        found = false
        break
      }
    }
    if found {
      return i
    }
  }
  -1
}

///|
/// Extract a simple attribute value from SVG string
fn extract_svg_attribute(svg : String, attr_name : String) -> String? {
  // Look for attr='value' or attr="value"
  let patterns = [attr_name + "='", attr_name + "=\""]
  for pattern in patterns {
    let idx = find_string_index(svg, pattern)
    if idx >= 0 {
      let start = idx + pattern.length()
      let quote_char = if pattern.has_suffix("'") { '\'' } else { '"' }
      // Find closing quote
      for end = start; end < svg.length(); end = end + 1 {
        if svg[end].to_int().unsafe_to_char() == quote_char {
          let result = svg.unsafe_substring(start~, end~)
          if result.length() > 0 {
            return Some(result)
          }
        }
      }
    }
  }
  None
}

///|
/// Parse viewBox attribute (format: "minX minY width height")
fn parse_viewbox(viewbox : String) -> (Double, Double)? {
  // Split by spaces
  let parts : Array[String] = []
  let mut current = StringBuilder::new()
  for i = 0; i < viewbox.length(); i = i + 1 {
    let c = viewbox[i].to_int().unsafe_to_char()
    if c == ' ' {
      if current.to_string().length() > 0 {
        parts.push(current.to_string())
        current = StringBuilder::new()
      }
    } else {
      current.write_char(c)
    }
  }
  if current.to_string().length() > 0 {
    parts.push(current.to_string())
  }
  if parts.length() >= 4 {
    let width = @string.parse_double(parts[2]) catch { _ => return None }
    let height = @string.parse_double(parts[3]) catch { _ => return None }
    if width > 0.0 && height > 0.0 {
      Some((width, height))
    } else {
      None
    }
  } else {
    None
  }
}

///|
/// Extract intrinsic size from SVG data URI
fn parse_svg_data_uri(src : String) -> (Double, Double)? {
  // Check if it's an SVG data URI
  if !src.has_prefix("data:image/svg+xml") {
    return None
  }
  // Check for base64 encoding (not supported yet)
  if src.contains(";base64,") {
    return None
  }
  // Find the comma that starts the data
  let mut comma_idx = -1
  for i = 0; i < src.length(); i = i + 1 {
    if src[i].to_int().unsafe_to_char() == ',' {
      comma_idx = i
      break
    }
  }
  if comma_idx < 0 {
    return None
  }
  // URL decode the SVG content
  let svg_encoded = src.unsafe_substring(start=comma_idx + 1, end=src.length())
  let svg = url_decode(svg_encoded)
  // Extract width, height, and viewBox
  let width_attr = extract_svg_attribute(svg, "width")
  let height_attr = extract_svg_attribute(svg, "height")
  let viewbox_attr = extract_svg_attribute(svg, "viewBox")
  // Parse width and height
  let parsed_width : Double? = match width_attr {
    Some(w) => parse_html_dimension(w)
    None => None
  }
  let parsed_height : Double? = match height_attr {
    Some(h) => parse_html_dimension(h)
    None => None
  }
  // Get aspect ratio from viewBox
  let viewbox_size : (Double, Double)? = match viewbox_attr {
    Some(vb) => parse_viewbox(vb)
    None => None
  }
  // Calculate final dimensions
  match (parsed_width, parsed_height, viewbox_size) {
    // Both width and height specified
    (Some(w), Some(h), _) => Some((w, h))
    // Only width specified, use viewBox aspect ratio
    (Some(w), None, Some((vb_w, vb_h))) => {
      let aspect = vb_w / vb_h
      Some((w, w / aspect))
    }
    // Only height specified, use viewBox aspect ratio
    (None, Some(h), Some((vb_w, vb_h))) => {
      let aspect = vb_w / vb_h
      Some((h * aspect, h))
    }
    // No width/height but viewBox exists
    (None, None, Some((vb_w, vb_h))) => Some((vb_w, vb_h))
    // Width only, no aspect ratio
    (Some(w), None, None) => Some((w, w))
    // Height only, no aspect ratio
    (None, Some(h), None) => Some((h, h))
    _ => None
  }
}

///|
fn base64_char_value(c : Char) -> Int? {
  let cp = c.to_int()
  if cp >= 'A'.to_int() && cp <= 'Z'.to_int() {
    Some(cp - 'A'.to_int())
  } else if cp >= 'a'.to_int() && cp <= 'z'.to_int() {
    Some(cp - 'a'.to_int() + 26)
  } else if cp >= '0'.to_int() && cp <= '9'.to_int() {
    Some(cp - '0'.to_int() + 52)
  } else if cp == '+'.to_int() {
    Some(62)
  } else if cp == '/'.to_int() {
    Some(63)
  } else {
    None
  }
}

///|
fn decode_base64_prefix(encoded : String, byte_count : Int) -> Array[Int]? {
  let bytes : Array[Int] = []
  let quartet : Array[Int] = []
  for c in encoded.iter() {
    if c == '=' {
      quartet.push(-1)
    } else {
      match base64_char_value(c) {
        Some(v) => quartet.push(v)
        None => continue
      }
    }
    if quartet.length() == 4 {
      let v0 = quartet[0]
      let v1 = quartet[1]
      let v2 = quartet[2]
      let v3 = quartet[3]
      if v0 < 0 || v1 < 0 {
        break
      }
      bytes.push(((v0 << 2) | (v1 >> 4)) & 0xFF)
      if v2 >= 0 {
        bytes.push((((v1 & 0x0F) << 4) | (v2 >> 2)) & 0xFF)
      }
      if v2 >= 0 && v3 >= 0 {
        bytes.push((((v2 & 0x03) << 6) | v3) & 0xFF)
      }
      if bytes.length() >= byte_count {
        return Some(bytes)
      }
      quartet.clear()
    }
  }
  None
}

///|
fn parse_gif_data_uri(src : String) -> (Double, Double)? {
  if !src.to_lower().has_prefix("data:image/gif;base64,") {
    return None
  }
  let mut comma_idx = -1
  for i = 0; i < src.length(); i = i + 1 {
    if src[i].to_int().unsafe_to_char() == ',' {
      comma_idx = i
      break
    }
  }
  if comma_idx < 0 {
    return None
  }
  let encoded = src.unsafe_substring(start=comma_idx + 1, end=src.length())
  match decode_base64_prefix(encoded, 10) {
    Some(bytes) => {
      if bytes.length() < 10 {
        return None
      }
      // GIF header + logical screen descriptor (little-endian width/height)
      if bytes[0] != 'G'.to_int() ||
        bytes[1] != 'I'.to_int() ||
        bytes[2] != 'F'.to_int() {
        return None
      }
      let width = bytes[6] + (bytes[7] << 8)
      let height = bytes[8] + (bytes[9] << 8)
      if width > 0 && height > 0 {
        Some((width.to_double(), height.to_double()))
      } else {
        None
      }
    }
    None => None
  }
}

///|
fn parse_png_data_uri(src : String) -> (Double, Double)? {
  if !src.to_lower().has_prefix("data:image/png;base64,") {
    return None
  }
  let mut comma_idx = -1
  for i = 0; i < src.length(); i = i + 1 {
    if src[i].to_int().unsafe_to_char() == ',' {
      comma_idx = i
      break
    }
  }
  if comma_idx < 0 {
    return None
  }
  let encoded = src.unsafe_substring(start=comma_idx + 1, end=src.length())
  match decode_base64_prefix(encoded, 24) {
    Some(bytes) => {
      if bytes.length() < 24 {
        return None
      }
      // PNG signature
      if bytes[0] != 0x89 ||
        bytes[1] != 0x50 ||
        bytes[2] != 0x4E ||
        bytes[3] != 0x47 ||
        bytes[4] != 0x0D ||
        bytes[5] != 0x0A ||
        bytes[6] != 0x1A ||
        bytes[7] != 0x0A {
        return None
      }
      // First chunk must be IHDR
      if bytes[12] != 'I'.to_int() ||
        bytes[13] != 'H'.to_int() ||
        bytes[14] != 'D'.to_int() ||
        bytes[15] != 'R'.to_int() {
        return None
      }
      let width = (bytes[16] << 24) |
        (bytes[17] << 16) |
        (bytes[18] << 8) |
        bytes[19]
      let height = (bytes[20] << 24) |
        (bytes[21] << 16) |
        (bytes[22] << 8) |
        bytes[23]
      if width > 0 && height > 0 {
        Some((width.to_double(), height.to_double()))
      } else {
        None
      }
    }
    None => None
  }
}

///|
/// Get intrinsic size from image src attribute
fn get_image_intrinsic_size_default(src : String) -> (Double, Double)? {
  // Restrict to data URI parsing to avoid filesystem/network side-effects.
  // SVG/GIF/PNG support compact intrinsic extraction.
  match parse_svg_data_uri(src) {
    Some(size) => Some(size)
    None =>
      match parse_gif_data_uri(src) {
        Some(size) => Some(size)
        None => parse_png_data_uri(src)
      }
  }
}

///|
/// Get intrinsic size from image src attribute, optionally using an external provider.
fn get_image_intrinsic_size(src : String) -> (Double, Double)? {
  // Replay short-circuits the (external) image resolver when active.
  match image_intrinsic_replay(src) {
    Some(replayed) => return replayed
    None => ()
  }
  let result = match image_intrinsic_size_provider_override.val {
    Some(provider) =>
      match (provider.func)(src) {
        Some(size) => Some(size)
        None => get_image_intrinsic_size_default(src)
      }
    None => get_image_intrinsic_size_default(src)
  }
  note_image_intrinsic_result(src, result)
  result
}